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

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

commit d80de8a97b97680f758d489770fcb93b67d5874e
Author: Harbs <[email protected]>
AuthorDate: Fri Jul 31 14:00:16 2026 +0300

    Add Code Graph Exporter and Related Classes
    
    - Introduced CodeGraphExporter for exporting code graph models from 
definitions.
    - Added CodeGraphModel to represent the structure of the code graph.
    - Implemented CodeGraphSymbol to encapsulate symbol details including 
members and parameters.
    - Created CodeGraphWriter for serializing the code graph model to a 
structured format.
    - Developed CodeGraphIdFactory for generating unique identifiers for 
definitions and members.
    - Added CodeGraphParameter and CodeGraphReference to represent function 
parameters and type references.
    - Implemented unit tests for CodeGraphExporter, CodeGraphIdFactory, and 
CodeGraphWriter to ensure correct functionality and output.
---
 CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md          | 375 +++++++++++++++++++++
 .../apache/royale/compiler/clients/CODEGRAPH.java  | 183 ++++++++++
 .../internal/codegen/graph/CodeGraphExporter.java  | 209 ++++++++++++
 .../internal/codegen/graph/CodeGraphIdFactory.java |  65 ++++
 .../internal/codegen/graph/CodeGraphModel.java     |  70 ++++
 .../internal/codegen/graph/CodeGraphParameter.java |  63 ++++
 .../internal/codegen/graph/CodeGraphReference.java |  56 +++
 .../internal/codegen/graph/CodeGraphSymbol.java    | 166 +++++++++
 .../internal/codegen/graph/CodeGraphWriter.java    | 297 ++++++++++++++++
 .../codegen/graph/TestCodeGraphExporter.java       | 111 ++++++
 .../codegen/graph/TestCodeGraphIdFactory.java      |  68 ++++
 .../codegen/graph/TestCodeGraphWriter.java         |  97 ++++++
 12 files changed, 1760 insertions(+)

diff --git a/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md 
b/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
new file mode 100644
index 000000000..f29b495c4
--- /dev/null
+++ b/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
@@ -0,0 +1,375 @@
+# Royale Code Graph Exporter Implementation Plan
+
+## Objective
+
+Add a compiler-backed exporter that produces deterministic, machine-readable 
descriptions of the public Apache Royale API. The output must be complete 
enough for clients to determine how to use every public class, interface, 
package function, field, accessor, method, event, style, effect, and MXML 
component, including all referenced types.
+
+The exporter belongs in `royale-compiler`. Project orchestration and release 
packaging belong in the sibling `royale-asjs` repository and should be handled 
only after the compiler exporter is stable.
+
+## Why This Must Use Compiler Semantics
+
+Do not parse ActionScript or MXML with regular expressions. The exporter must 
use resolved compiler definitions because Royale APIs may depend on:
+
+- `COMPILE::JS` and `COMPILE::SWF` conditional compilation.
+- Imports, namespaces, package functions, and external SWCs.
+- Inheritance, interface implementation, and overrides.
+- Getter/setter pairs and target-specific signatures.
+- ASDoc tags such as `@copy` and `@private`.
+- Structured metadata such as `Event`, `Style`, `Effect`, `Bindable`, 
`DefaultProperty`, and `Inspectable`.
+- Definitions that are reachable or exported by a SWC rather than every source 
declaration.
+
+`UIBase` in `royale-asjs/frameworks/projects/Basic` is an important eventual 
integration case. Its `typeNames` field is simple, while `width`, `parent`, and 
`transformElement` demonstrate target-specific declarations and inheritance 
behavior.
+
+## Compiler Infrastructure and References
+
+Start by reading these files:
+
+- 
`compiler-jx/src/main/java/org/apache/royale/compiler/clients/MXMLJSCRoyale.java`
+- 
`compiler-jx/src/main/java/org/apache/royale/compiler/internal/driver/mxml/royale/MXMLRoyaleSWCBackend.java`
+- 
`compiler-jx/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleJSProject.java`
+- 
`compiler-jx/src/main/java/org/apache/royale/compiler/internal/targets/RoyaleSWCTarget.java`
+- 
`compiler-jx/src/main/java/org/apache/royale/compiler/asdoc/royale/ASDocComment.java`
+- 
`compiler-jx/src/main/java/org/apache/royale/compiler/internal/parsing/as/RoyaleASDocDelegate.java`
+
+The compiler infrastructure already provides:
+
+1. Normal compiler configuration and workspace setup.
+2. `RoyaleSWCTarget` construction.
+3. Target roots through `getReachableCompilationUnits(...)`.
+4. Compiler-project ordering of reachable units.
+5. Extern and external-linkage information.
+6. Separate resolved JS and SWF configurations.
+7. Parsed ASDoc comments through the compiler delegate and comment model.
+
+Do not create an unrelated parser or duplicate compiler setup. Reuse the 
lower-level compiler lifecycle where appropriate, but keep the code graph 
client independent from the ASDoc client and output model. `CODEGRAPH` is not a 
kind of `ASDOCJSC`; any shared behavior is incidental compiler infrastructure 
and does not justify inheritance. The code graph path must not require 
ASDoc-specific configuration, emitters, or exclusions unless a rule is 
independently part of the code graph contract.
+
+## Recommended Shape
+
+Keep the first implementation in `compiler-jx` because that module contains 
the Royale compiler client, SWC backend, project, and target infrastructure 
needed by the exporter.
+
+The standalone client should extend the common compiler client infrastructure, 
use the normal Royale SWC backend, and own its target setup, reachable-unit 
selection, and graph output. Existing clients such as `ASDOCJSC` should remain 
unchanged unless a genuinely shared lower-level abstraction is introduced for 
multiple compiler clients.
+
+Suggested classes are names, not mandatory API decisions:
+
+```text
+org.apache.royale.compiler.clients.CODEGRAPH
+org.apache.royale.compiler.internal.codegen.graph.CodeGraphExporter
+org.apache.royale.compiler.internal.codegen.graph.CodeGraphModel
+org.apache.royale.compiler.internal.codegen.graph.CodeGraphWriter
+```
+
+Prefer a small model and writer over embedding JSON calls throughout AST 
visitors. Compiler traversal should populate the model; serialization should be 
deterministic and independently testable.
+
+The initial command should resemble existing compiler clients:
+
+```sh
+java -cp ... org.apache.royale.compiler.clients.CODEGRAPH \
+  -load-config+=path/to/config.xml \
+  -compiler.define+=COMPILE::JS,true \
+  -compiler.define+=COMPILE::SWF,false \
+  -output=target/codegraph/graph.js.json
+```
+
+One invocation exports one resolved target. Merging JS and SWF into one 
logical release index can come later. Keeping target runs separate matches 
current compiler and Maven behavior and avoids inventing a second 
conditional-compilation evaluator.
+
+## First Vertical Slice
+
+The first pull request should prove semantic extraction, not solve release 
packaging.
+
+1. Add a compiler test fixture containing:
+   - One public class and one interface.
+   - A base class and inherited member.
+   - A constructor.
+   - A public variable with a default value.
+   - A constant.
+   - Getter and setter declarations.
+   - A method with required, optional, and rest parameters.
+   - A package-level function.
+   - `Event`, `Bindable`, and `DefaultProperty` metadata.
+   - An ASDoc description and tags.
+   - A JS-only member and a SWF-only member.
+   - A reference to a type from an external SWC.
+
+2. Build the fixture through the normal compiler target.
+
+3. Enumerate only reachable AS/MXML compilation units using the normal SWC 
target, extern configuration, and external-linkage rules owned by the code 
graph client.
+
+4. Export public top-level definitions and their directly declared public 
members.
+
+5. Resolve every referenced type through `ICompilerProject`. Emit a stable 
symbol reference even when the definition belongs to an external library.
+
+6. Serialize a deterministic JSON document.
+
+7. Assert the complete output as a golden fixture and run the export twice to 
verify byte-identical output.
+
+Do not begin with inherited-member materialization, Maven attachment, graph 
compression, npm packaging, or all Royale framework projects. Those are 
follow-up slices.
+
+## Minimum Version 1 Data Model
+
+The exact JSON shape should be finalized with tests, but version 1 needs these 
concepts.
+
+### Document
+
+```json
+{
+  "schemaVersion": "1.0",
+  "target": "js",
+  "module": null,
+  "symbols": [],
+  "externalSymbols": []
+}
+```
+
+Do not include timestamps or absolute machine paths. They break deterministic 
release artifacts.
+
+### Stable Symbol IDs
+
+Use qualified semantic identities rather than source locations:
+
+```text
+as3://org.apache.royale.core/UIBase
+as3://org.apache.royale.core/UIBase#typeNames
+as3://org.apache.royale.core/UIBase#width:get
+as3://org.apache.royale.core/UIBase#setWidth(Number,Boolean)
+as3://org.apache.royale.utils/sendEvent
+```
+
+Overloads are uncommon in AS3 but IDs must still distinguish callable 
signatures. Constructors, getters, setters, methods, fields, constants, and 
package functions need unambiguous IDs.
+
+### Definition Data
+
+For each public type or package-level definition, include:
+
+- Stable ID, qualified name, base name, package, and kind.
+- Namespace/visibility.
+- Declaring source using a repository-relative or configured 
source-root-relative path.
+- Base type and implemented interfaces as symbol references.
+- Flags such as static, final, dynamic, override, abstract, and native when 
available.
+- Parsed ASDoc description and structured tags.
+- Structured metadata with ordered key/value arguments.
+- Directly declared members.
+
+For members, include:
+
+- Kind, name, stable ID, and declaring type.
+- Type or return type as a resolved symbol reference.
+- Ordered parameters with type, optional/rest state, and default value 
representation.
+- Getter/setter identity.
+- Constant or field default value when available.
+- ASDoc and metadata.
+
+For external references, emit at least:
+
+- Stable ID.
+- Qualified name.
+- Kind when known.
+- Origin/library path in portable form when available.
+- A marker that the full declaration is external to this graph.
+
+Never silently replace an unresolved type with a simple string. Emit an 
explicit unresolved reference and a compiler problem so validation can find it.
+
+## Compiler APIs to Prefer
+
+Use public definition and scope interfaces where possible:
+
+- `IDefinition`
+- `ITypeDefinition`
+- `IClassDefinition`
+- `IInterfaceDefinition`
+- `IFunctionDefinition`
+- `IAccessorDefinition`
+- `IVariableDefinition`
+- `IConstantDefinition`
+- `IParameterDefinition`
+- `IMetaTag` and metadata attribute APIs
+- `ICompilerProject`
+- `ICompilationUnit`
+
+Resolve types with the active compiler project. Do not infer qualification 
from source imports manually.
+
+Use AST nodes only for facts absent from definitions, such as preserving a 
source-level default expression. Keep semantic identity and type resolution 
definition-based.
+
+## Public API Rules
+
+Initially include:
+
+- Public top-level classes and interfaces.
+- Public constructors and members.
+- Public package functions, variables, and constants.
+- Metadata that affects client usage.
+
+Initially exclude:
+
+- Private, protected, and internal declarations.
+- Declarations excluded from the public documentation contract, including 
`@private`.
+- Compiler-generated implementation details unless they are actually part of 
the exported public SWC API.
+- Method bodies and local-variable dependency graphs.
+
+The objective is a public API/type graph, not a whole-program call graph.
+
+## ASDoc Handling
+
+Use `RoyaleASDocDelegate` and the existing parsed comment model for 
documentation extraction only. This does not make the exporter an ASDoc client 
and must not require the ASDoc backend or ASDoc configuration class. Preserve 
descriptions and tags structurally.
+
+For the first slice, retain `@copy` as a structured tag/reference. Resolve and 
materialize copied text in a follow-up only after direct comments are correct. 
Similarly, preserve unknown tags rather than dropping them.
+
+Treat `@private` consistently with the compiler's parsed documentation 
semantics.
+
+## Target Handling
+
+Run the exporter once per target configuration:
+
+```text
+COMPILE::JS=true,  COMPILE::SWF=false -> graph.js.json
+COMPILE::JS=false, COMPILE::SWF=true  -> graph.swf.json
+```
+
+Each graph describes what the compiler actually sees for that target. A later 
merger may combine matching stable IDs and mark availability as `js`, `swf`, or 
both.
+
+Do not make the exporter inspect inactive conditional branches itself.
+
+## Determinism Requirements
+
+- Sort top-level symbols by stable ID.
+- Sort members by stable ID, not hash-map iteration order.
+- Preserve parameter order and metadata argument order where order is 
meaningful.
+- Normalize path separators to `/`.
+- Omit timestamps, temporary paths, and absolute checkout paths.
+- Use a fixed JSON encoding and newline policy.
+- Fail tests if two runs produce different bytes.
+
+## Tests
+
+Place focused tests under `compiler-jx/src/test/java` and fixtures under the 
existing compiler-jx test resource conventions.
+
+Required first tests:
+
+1. `typeNames`-style public field: type, default value, docs, metadata, owner.
+2. Class/interface inheritance and declared-member ownership.
+3. Getter and setter represented distinctly but linked by property name.
+4. Parameter types, defaults, optional parameters, and rest parameters.
+5. Package-level function.
+6. JS/SWF conditional members produce different target outputs.
+7. External type reference is retained.
+8. Excluded/private definitions are absent.
+9. Deterministic byte output.
+10. Unresolved types create an explicit problem or validation failure.
+
+The tests must also cover the standalone client path, not only graph model 
helpers:
+
+- Normal compiler configuration and include options, without ASDoc-only 
options.
+- SWC target setup and reachable-unit filtering.
+- Output path handling and target identification.
+- No graph output after compiler errors unless `create-target-with-errors` 
explicitly permits it.
+- A target-built golden graph, in addition to focused in-memory definition 
tests.
+
+Use explicit assertions in test helpers so a missing symbol or member reports 
its semantic identity instead of failing later with a null-pointer exception. 
Exact JSON golden assertions are appropriate because byte-level stability is 
part of the exporter contract.
+
+Run the narrow module tests first:
+
+```sh
+./mvnw -pl compiler-jx -am test
+```
+
+Follow repository conventions if the existing compiler test harness requires 
additional environment properties.
+
+## Repository Conventions and Review Gate
+
+Before considering the first compiler PR review-ready:
+
+- Follow the existing Apache headers, package layout, four-space indentation, 
brace placement, explicit generic types, `Test*` naming, JUnit 4, and compiler 
test-base patterns.
+- Keep imports consistent; do not use fully qualified collection types inline 
when normal imports are already used.
+- Add concise class-level Javadocs to the new production classes, consistent 
with neighboring compiler code.
+- Check compiler problems after target construction and before writing output. 
Match neighboring client behavior for `create-target-with-errors`.
+- Keep `ASDOCJSC` unchanged. The code graph client must not inherit from it or 
use `MXMLRoyaleASDocBackend`.
+- Keep deterministic writer tests, but supplement them with compiler-backed 
and CLI-level tests.
+- Run editor diagnostics and the complete `compiler-jx` reactor tests before 
review.
+
+Passing the existing suite is necessary but not sufficient: the new client 
must have direct regression coverage for configuration, target setup, 
filtering, error handling, and output generation.
+
+## Suggested Pull Request Sequence
+
+### PR 1: Semantic exporter MVP
+
+- Graph model and deterministic JSON writer.
+- Compiler-backed collection of public types and directly declared members.
+- ASDoc and metadata extraction.
+- JS/SWF fixture tests.
+- CLI entry point in `compiler-jx`.
+
+### PR 2: Completeness
+
+- Package-level definitions.
+- External and unresolved symbol records.
+- Inheritance/override edges.
+- Effective inherited public member view if clients require it.
+- `@copy` resolution.
+- JSON Schema and schema compatibility policy.
+
+### PR 3: Build-tool integration
+
+- Add a `compile-codegraph` goal to `royale-maven-plugin`, modeled after 
`CompileASDocMojo`.
+- Add Ant/tool registration and SDK launcher scripts.
+- Ensure both target configurations select the same dependency classifiers as 
`CompileASDocMojo`.
+
+### PR 4: `royale-asjs` integration
+
+This work happens in the sibling repository:
+
+- Generate one graph per framework project and target.
+- Add module/Maven coordinates and dependency closure to an aggregate index.
+- Add MXML manifest URI/tag mappings.
+- Validate exports against released SWCs.
+- Package identical graph data into Maven classifiers, SDK downloads, and 
`@apache-royale/codegraphs` on npm.
+- Add release hashes and signatures.
+
+## Integration Contract With `royale-asjs`
+
+The compiler exporter should accept normal compiler configuration and produce 
one graph file. It should not need to know the Royale reactor, npm package 
layout, or release staging paths.
+
+`royale-asjs` will be responsible for supplying:
+
+- Project source paths and include classes.
+- JS or SWF dependency paths.
+- Compiler defines.
+- Project/module identity.
+- Manifest files and MXML namespace mapping if not already available through 
compiler configuration.
+- Final output location and release packaging.
+
+The graph format must allow `royale-asjs` to add module metadata without 
rewriting semantic symbol records.
+
+## First Session Checklist
+
+1. Build `compiler-jx` unchanged and record the working test command.
+2. Run a normal SWC test configuration on a tiny fixture.
+3. Trace one reachable compilation unit from `RoyaleSWCTarget` to its resolved 
top-level definition.
+4. Prove extraction of class name, base class, interfaces, and one public 
field into an in-memory model.
+5. Add a deterministic writer and golden test.
+6. Add JS/SWF conditional fixture coverage.
+7. Only then add the standalone `CODEGRAPH` client.
+
+## Definition of Done for the Compiler Phase
+
+The compiler phase is ready for `royale-asjs` integration when:
+
+- A clean compiler checkout can invoke one documented command to generate a 
graph.
+- JS and SWF runs reflect their active conditional declarations.
+- All public signatures in the test fixture retain resolved type references.
+- ASDoc and relevant metadata are present.
+- External and unresolved types are explicit.
+- Output is byte-identical across repeated runs.
+- The `compiler-jx` test suite passes.
+- No application-specific or `royale-asjs` project list is hard-coded in the 
exporter.
+
+## Out of Scope for the First Compiler PR
+
+- Rendering HTML documentation.
+- Application call graphs or method-body analysis.
+- npm publication.
+- Maven artifact attachment.
+- SDK release assembly.
+- Compressing graph files.
+- Combining all Royale projects.
+- Replacing current ASDoc output.
+
+Keep the first change narrow: resolved public compiler facts in deterministic 
JSON.
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
new file mode 100644
index 000000000..662d2891b
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
@@ -0,0 +1,183 @@
+/*
+ *
+ *  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.clients;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.internal.codegen.graph.CodeGraphExporter;
+import org.apache.royale.compiler.internal.codegen.graph.CodeGraphModel;
+import org.apache.royale.compiler.internal.codegen.graph.CodeGraphWriter;
+import 
org.apache.royale.compiler.internal.driver.mxml.royale.MXMLRoyaleSWCBackend;
+import org.apache.royale.compiler.internal.targets.RoyaleSWCTarget;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.problems.InternalCompilerProblem;
+import org.apache.royale.compiler.targets.ITarget.TargetType;
+import org.apache.royale.compiler.targets.ITargetSettings;
+import org.apache.royale.compiler.units.ICompilationUnit;
+
+public class CODEGRAPH extends MXMLJSCRoyale
+{
+    public CODEGRAPH()
+    {
+        super(new MXMLRoyaleSWCBackend());
+    }
+
+    @Override
+    public String getName()
+    {
+        return "codegraph";
+    }
+
+    @Override
+    public int execute(String[] args)
+    {
+        return staticMainNoExit(args);
+    }
+
+    public static void main(final String[] args)
+    {
+        System.exit(staticMainNoExit(args));
+    }
+
+    public static int staticMainNoExit(final String[] args)
+    {
+        final CODEGRAPH codeGraph = new CODEGRAPH();
+        final List<ICompilerProblem> problems = new 
ArrayList<ICompilerProblem>();
+        return codeGraph.mainNoExit(args, problems, true);
+    }
+
+    @Override
+    protected boolean compile()
+    {
+        try
+        {
+            
project.getSourceCompilationUnitFactory().addHandler(asFileHandler);
+            if (!setupTargetFile())
+                return false;
+            buildArtifact();
+            if (jsTarget == null)
+                return false;
+
+            Collection<IDefinition> definitions = new ArrayList<IDefinition>();
+            for (ICompilationUnit compilationUnit : 
getReachableCompilationUnits())
+            {
+                
definitions.addAll(compilationUnit.getFileScopeRequest().get().getExternallyVisibleDefinitions());
+            }
+
+            CodeGraphModel model = new 
CodeGraphExporter(project).export(definitions, getGraphTarget(), null);
+            File outputFile = getGraphOutputFile();
+            File parent = outputFile.getParentFile();
+            if (parent != null && !parent.exists())
+                parent.mkdirs();
+            Writer writer = new BufferedWriter(new OutputStreamWriter(new 
FileOutputStream(outputFile), "UTF-8"));
+            try
+            {
+                new CodeGraphWriter().write(model, writer);
+            }
+            finally
+            {
+                writer.close();
+            }
+            return true;
+        }
+        catch (Exception exception)
+        {
+            problems.add(new InternalCompilerProblem(exception));
+            return false;
+        }
+    }
+
+    private Collection<ICompilationUnit> getReachableCompilationUnits() throws 
InterruptedException
+    {
+        Set<String> externs = config.getExterns();
+        Collection<ICompilationUnit> roots = 
((RoyaleSWCTarget)target).getReachableCompilationUnits(problems.getProblems());
+        Collection<ICompilationUnit> reachableCompilationUnits = 
project.getReachableCompilationUnitsInSWFOrder(roots);
+        Collection<ICompilationUnit> result = new 
ArrayList<ICompilationUnit>();
+        for (ICompilationUnit compilationUnit : reachableCompilationUnits)
+        {
+            ICompilationUnit.UnitType unitType = 
compilationUnit.getCompilationUnitType();
+            if (unitType != ICompilationUnit.UnitType.AS_UNIT
+                    && unitType != ICompilationUnit.UnitType.MXML_UNIT)
+                continue;
+            if (externs.contains(compilationUnit.getQualifiedNames().get(0)))
+                continue;
+            if (project.isExternalLinkage(compilationUnit))
+                continue;
+            result.add(compilationUnit);
+        }
+        return result;
+    }
+
+    @Override
+    protected boolean setupTargetFile() throws InterruptedException
+    {
+        ITargetSettings settings = getCodeGraphTargetSettings();
+        if (settings == null)
+            return false;
+        project.setTargetSettings(settings);
+        target = project.getBackend().createTarget(project, settings, null);
+        return true;
+    }
+
+    private ITargetSettings getCodeGraphTargetSettings()
+    {
+        if (targetSettings == null)
+            targetSettings = 
projectConfigurator.getTargetSettings(getTargetType());
+        if (targetSettings == null)
+            problems.addAll(projectConfigurator.getConfigurationProblems());
+        return targetSettings;
+    }
+
+    @Override
+    protected void validateTargetFile()
+    {
+    }
+
+    @Override
+    protected TargetType getTargetType()
+    {
+        return TargetType.SWC;
+    }
+
+    private String getGraphTarget()
+    {
+        Map<String, String> definitions = config.getCompilerDefine();
+        return definitions != null && 
"true".equals(definitions.get("COMPILE::SWF")) ? "swf" : "js";
+    }
+
+    private File getGraphOutputFile()
+    {
+        if (config.getOutput() != null)
+            return new File(config.getOutput());
+        return new File(FilenameUtils.removeExtension(config.getTargetFile()) 
+ ".codegraph.json");
+    }
+
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
new file mode 100644
index 000000000..b987ad54a
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
@@ -0,0 +1,209 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.royale.compiler.definitions.IClassDefinition;
+import org.apache.royale.compiler.definitions.IConstantDefinition;
+import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.definitions.IFunctionDefinition;
+import org.apache.royale.compiler.definitions.IGetterDefinition;
+import org.apache.royale.compiler.definitions.IInterfaceDefinition;
+import org.apache.royale.compiler.definitions.IParameterDefinition;
+import org.apache.royale.compiler.definitions.ISetterDefinition;
+import org.apache.royale.compiler.definitions.ITypeDefinition;
+import org.apache.royale.compiler.definitions.IVariableDefinition;
+import org.apache.royale.compiler.projects.ICompilerProject;
+
+public final class CodeGraphExporter
+{
+    private final ICompilerProject project;
+    private final Set<String> exportedQualifiedNames = new HashSet<String>();
+    private final Map<String, ITypeDefinition> externalDefinitions = new 
HashMap<String, ITypeDefinition>();
+
+    public CodeGraphExporter(ICompilerProject project)
+    {
+        this.project = project;
+    }
+
+    public CodeGraphModel export(Collection<IDefinition> definitions, String 
target, String module)
+    {
+        CodeGraphModel model = new CodeGraphModel(target, module);
+        exportedQualifiedNames.clear();
+        externalDefinitions.clear();
+        for (IDefinition definition : definitions)
+        {
+            if (definition.isPublic() && isSupportedType(definition))
+                exportedQualifiedNames.add(definition.getQualifiedName());
+        }
+        for (IDefinition definition : definitions)
+        {
+            if (definition.isPublic() && isSupportedType(definition))
+                model.addSymbol(exportType((ITypeDefinition)definition));
+        }
+        for (ITypeDefinition externalDefinition : externalDefinitions.values())
+        {
+            CodeGraphSymbol externalSymbol = createSymbol(externalDefinition, 
getTypeKind(externalDefinition));
+            externalSymbol.setExternal(true);
+            model.addExternalSymbol(externalSymbol);
+        }
+        return model;
+    }
+
+    private boolean isSupportedType(IDefinition definition)
+    {
+        return definition instanceof IClassDefinition || definition instanceof 
IInterfaceDefinition;
+    }
+
+    private CodeGraphSymbol exportType(ITypeDefinition definition)
+    {
+        CodeGraphSymbol symbol = createSymbol(definition, 
getTypeKind(definition));
+        if (definition instanceof IClassDefinition)
+        {
+            IClassDefinition classDefinition = (IClassDefinition)definition;
+            IClassDefinition baseClass = 
classDefinition.resolveBaseClass(project);
+            if (baseClass != null)
+                symbol.setBaseType(createReference(baseClass));
+            for (IInterfaceDefinition interfaceDefinition : 
classDefinition.resolveImplementedInterfaces(project))
+                symbol.addInterface(createReference(interfaceDefinition));
+            IFunctionDefinition constructor = classDefinition.getConstructor();
+            if (constructor != null && !constructor.isImplicit())
+                symbol.addMember(exportFunction(constructor, definition));
+        }
+        else
+        {
+            IInterfaceDefinition interfaceDefinition = 
(IInterfaceDefinition)definition;
+            for (IInterfaceDefinition extendedInterface : 
interfaceDefinition.resolveExtendedInterfaces(project))
+                symbol.addInterface(createReference(extendedInterface));
+        }
+
+        for (IDefinition memberDefinition : 
definition.getContainedScope().getAllLocalDefinitions())
+        {
+            boolean isConstructor = memberDefinition instanceof 
IFunctionDefinition
+                    && ((IFunctionDefinition)memberDefinition).isConstructor();
+                if (!memberDefinition.isPublic() || 
memberDefinition.isImplicit() || isConstructor)
+                continue;
+            if (memberDefinition instanceof IFunctionDefinition)
+                
symbol.addMember(exportFunction((IFunctionDefinition)memberDefinition, 
definition));
+            else if (memberDefinition instanceof IVariableDefinition)
+                
symbol.addMember(exportVariable((IVariableDefinition)memberDefinition, 
definition));
+        }
+        return symbol;
+    }
+
+    private CodeGraphSymbol exportFunction(IFunctionDefinition definition, 
ITypeDefinition declaringType)
+    {
+        String kind;
+        String id;
+        if (definition instanceof IGetterDefinition)
+        {
+            kind = "getter";
+            id = CodeGraphIdFactory.accessor(declaringType.getQualifiedName(), 
definition.getBaseName(), true);
+        }
+        else if (definition instanceof ISetterDefinition)
+        {
+            kind = "setter";
+            id = CodeGraphIdFactory.accessor(declaringType.getQualifiedName(), 
definition.getBaseName(), false);
+        }
+        else
+        {
+            kind = definition.isConstructor() ? "constructor" : "method";
+            id = createCallableId(definition, declaringType);
+        }
+
+        CodeGraphSymbol symbol = new CodeGraphSymbol(id, 
definition.getQualifiedName(), definition.getBaseName(),
+                definition.getPackageName(), kind);
+        symbol.setDeclaringType(createReference(declaringType));
+        if (definition instanceof IGetterDefinition || definition instanceof 
ISetterDefinition)
+        {
+            ITypeDefinition typeDefinition = definition.resolveType(project);
+            if (typeDefinition != null)
+                symbol.setType(createReference(typeDefinition));
+        }
+        else if (!definition.isConstructor())
+        {
+            ITypeDefinition returnType = definition.resolveReturnType(project);
+            if (returnType != null)
+                symbol.setReturnType(createReference(returnType));
+        }
+        for (IParameterDefinition parameterDefinition : 
definition.getParameters())
+        {
+            ITypeDefinition parameterType = 
parameterDefinition.resolveType(project);
+            CodeGraphReference typeReference = parameterType == null ? null : 
createReference(parameterType);
+            Object defaultValue = parameterDefinition.hasDefaultValue()
+                    ? parameterDefinition.resolveDefaultValue(project) : null;
+            symbol.addParameter(new 
CodeGraphParameter(parameterDefinition.getBaseName(), typeReference,
+                    parameterDefinition.hasDefaultValue(), 
parameterDefinition.isRest(), defaultValue));
+        }
+        return symbol;
+    }
+
+    private String createCallableId(IFunctionDefinition definition, 
ITypeDefinition declaringType)
+    {
+        java.util.List<String> parameterTypes = new 
java.util.ArrayList<String>();
+        for (IParameterDefinition parameterDefinition : 
definition.getParameters())
+        {
+            ITypeDefinition parameterType = 
parameterDefinition.resolveType(project);
+            parameterTypes.add(parameterType == null
+                    ? parameterDefinition.getTypeAsDisplayString() : 
parameterType.getQualifiedName());
+        }
+        if (definition.isConstructor())
+            return 
CodeGraphIdFactory.constructor(declaringType.getQualifiedName(), 
parameterTypes);
+        return CodeGraphIdFactory.callable(declaringType.getQualifiedName(), 
definition.getBaseName(), parameterTypes);
+    }
+
+    private CodeGraphSymbol exportVariable(IVariableDefinition definition, 
ITypeDefinition declaringType)
+    {
+        String kind = definition instanceof IConstantDefinition ? "constant" : 
"field";
+        CodeGraphSymbol symbol = new CodeGraphSymbol(
+                CodeGraphIdFactory.member(declaringType.getQualifiedName(), 
definition.getBaseName()),
+                definition.getQualifiedName(), definition.getBaseName(), 
definition.getPackageName(), kind);
+        symbol.setDeclaringType(createReference(declaringType));
+        ITypeDefinition typeDefinition = definition.resolveType(project);
+        if (typeDefinition != null)
+            symbol.setType(createReference(typeDefinition));
+        return symbol;
+    }
+
+    private CodeGraphSymbol createSymbol(IDefinition definition, String kind)
+    {
+        return new 
CodeGraphSymbol(CodeGraphIdFactory.definition(definition.getQualifiedName()),
+                definition.getQualifiedName(), definition.getBaseName(), 
definition.getPackageName(), kind);
+    }
+
+    private String getTypeKind(ITypeDefinition definition)
+    {
+        return definition instanceof IClassDefinition ? "class" : "interface";
+    }
+
+    private CodeGraphReference createReference(ITypeDefinition definition)
+    {
+        String qualifiedName = definition.getQualifiedName();
+        boolean external = !exportedQualifiedNames.contains(qualifiedName);
+        if (external)
+            externalDefinitions.put(qualifiedName, definition);
+        return new 
CodeGraphReference(CodeGraphIdFactory.definition(qualifiedName), qualifiedName, 
external, false);
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphIdFactory.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphIdFactory.java
new file mode 100644
index 000000000..ed745c4f9
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphIdFactory.java
@@ -0,0 +1,65 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import java.util.List;
+
+public final class CodeGraphIdFactory
+{
+    private static final String SCHEME = "as3://";
+
+    private CodeGraphIdFactory()
+    {
+    }
+
+    public static String definition(String qualifiedName)
+    {
+        return SCHEME + qualifiedName.replace('.', '/');
+    }
+
+    public static String member(String ownerQualifiedName, String memberName)
+    {
+        return definition(ownerQualifiedName) + "#" + memberName;
+    }
+
+    public static String accessor(String ownerQualifiedName, String 
propertyName, boolean getter)
+    {
+        return member(ownerQualifiedName, propertyName) + (getter ? ":get" : 
":set");
+    }
+
+    public static String callable(String ownerQualifiedName, String 
callableName, List<String> parameterTypes)
+    {
+        StringBuilder result = new StringBuilder(member(ownerQualifiedName, 
callableName));
+        result.append('(');
+        for (int i = 0; i < parameterTypes.size(); i++)
+        {
+            if (i > 0)
+                result.append(',');
+            result.append(parameterTypes.get(i));
+        }
+        result.append(')');
+        return result.toString();
+    }
+
+    public static String constructor(String ownerQualifiedName, List<String> 
parameterTypes)
+    {
+        return callable(ownerQualifiedName, "constructor", parameterTypes);
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphModel.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphModel.java
new file mode 100644
index 000000000..11babebec
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphModel.java
@@ -0,0 +1,70 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class CodeGraphModel
+{
+    public static final String SCHEMA_VERSION = "1.0";
+
+    private final String target;
+    private final String module;
+    private final List<CodeGraphSymbol> symbols = new 
ArrayList<CodeGraphSymbol>();
+    private final List<CodeGraphSymbol> externalSymbols = new 
ArrayList<CodeGraphSymbol>();
+
+    public CodeGraphModel(String target, String module)
+    {
+        this.target = target;
+        this.module = module;
+    }
+
+    public String getTarget()
+    {
+        return target;
+    }
+
+    public String getModule()
+    {
+        return module;
+    }
+
+    public void addSymbol(CodeGraphSymbol symbol)
+    {
+        symbols.add(symbol);
+    }
+
+    public List<CodeGraphSymbol> getSymbols()
+    {
+        return Collections.unmodifiableList(symbols);
+    }
+
+    public void addExternalSymbol(CodeGraphSymbol symbol)
+    {
+        externalSymbols.add(symbol);
+    }
+
+    public List<CodeGraphSymbol> getExternalSymbols()
+    {
+        return Collections.unmodifiableList(externalSymbols);
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphParameter.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphParameter.java
new file mode 100644
index 000000000..d12244315
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphParameter.java
@@ -0,0 +1,63 @@
+/*
+ *
+ *  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.codegen.graph;
+
+public final class CodeGraphParameter
+{
+    private final String name;
+    private final CodeGraphReference type;
+    private final boolean optional;
+    private final boolean rest;
+    private final Object defaultValue;
+
+    public CodeGraphParameter(String name, CodeGraphReference type, boolean 
optional, boolean rest, Object defaultValue)
+    {
+        this.name = name;
+        this.type = type;
+        this.optional = optional;
+        this.rest = rest;
+        this.defaultValue = defaultValue;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public CodeGraphReference getType()
+    {
+        return type;
+    }
+
+    public boolean isOptional()
+    {
+        return optional;
+    }
+
+    public boolean isRest()
+    {
+        return rest;
+    }
+
+    public Object getDefaultValue()
+    {
+        return defaultValue;
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphReference.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphReference.java
new file mode 100644
index 000000000..884e9d0be
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphReference.java
@@ -0,0 +1,56 @@
+/*
+ *
+ *  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.codegen.graph;
+
+public final class CodeGraphReference
+{
+    private final String id;
+    private final String qualifiedName;
+    private final boolean external;
+    private final boolean unresolved;
+
+    public CodeGraphReference(String id, String qualifiedName, boolean 
external, boolean unresolved)
+    {
+        this.id = id;
+        this.qualifiedName = qualifiedName;
+        this.external = external;
+        this.unresolved = unresolved;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public String getQualifiedName()
+    {
+        return qualifiedName;
+    }
+
+    public boolean isExternal()
+    {
+        return external;
+    }
+
+    public boolean isUnresolved()
+    {
+        return unresolved;
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphSymbol.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphSymbol.java
new file mode 100644
index 000000000..aa860608a
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphSymbol.java
@@ -0,0 +1,166 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class CodeGraphSymbol
+{
+    private final String id;
+    private final String qualifiedName;
+    private final String baseName;
+    private final String packageName;
+    private final String kind;
+    private boolean external;
+    private String source;
+    private CodeGraphReference declaringType;
+    private CodeGraphReference type;
+    private CodeGraphReference returnType;
+    private CodeGraphReference baseType;
+    private final List<CodeGraphReference> interfaces = new 
ArrayList<CodeGraphReference>();
+    private final List<CodeGraphParameter> parameters = new 
ArrayList<CodeGraphParameter>();
+    private final List<CodeGraphSymbol> members = new 
ArrayList<CodeGraphSymbol>();
+
+    public CodeGraphSymbol(String id, String qualifiedName, String baseName, 
String packageName, String kind)
+    {
+        this.id = id;
+        this.qualifiedName = qualifiedName;
+        this.baseName = baseName;
+        this.packageName = packageName;
+        this.kind = kind;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public String getQualifiedName()
+    {
+        return qualifiedName;
+    }
+
+    public String getBaseName()
+    {
+        return baseName;
+    }
+
+    public String getPackageName()
+    {
+        return packageName;
+    }
+
+    public String getKind()
+    {
+        return kind;
+    }
+
+    public boolean isExternal()
+    {
+        return external;
+    }
+
+    public void setExternal(boolean external)
+    {
+        this.external = external;
+    }
+
+    public String getSource()
+    {
+        return source;
+    }
+
+    public void setSource(String source)
+    {
+        this.source = source;
+    }
+
+    public CodeGraphReference getDeclaringType()
+    {
+        return declaringType;
+    }
+
+    public void setDeclaringType(CodeGraphReference declaringType)
+    {
+        this.declaringType = declaringType;
+    }
+
+    public CodeGraphReference getType()
+    {
+        return type;
+    }
+
+    public void setType(CodeGraphReference type)
+    {
+        this.type = type;
+    }
+
+    public CodeGraphReference getReturnType()
+    {
+        return returnType;
+    }
+
+    public void setReturnType(CodeGraphReference returnType)
+    {
+        this.returnType = returnType;
+    }
+
+    public CodeGraphReference getBaseType()
+    {
+        return baseType;
+    }
+
+    public void setBaseType(CodeGraphReference baseType)
+    {
+        this.baseType = baseType;
+    }
+
+    public void addInterface(CodeGraphReference interfaceReference)
+    {
+        interfaces.add(interfaceReference);
+    }
+
+    public List<CodeGraphReference> getInterfaces()
+    {
+        return Collections.unmodifiableList(interfaces);
+    }
+
+    public void addParameter(CodeGraphParameter parameter)
+    {
+        parameters.add(parameter);
+    }
+
+    public List<CodeGraphParameter> getParameters()
+    {
+        return Collections.unmodifiableList(parameters);
+    }
+
+    public void addMember(CodeGraphSymbol member)
+    {
+        members.add(member);
+    }
+
+    public List<CodeGraphSymbol> getMembers()
+    {
+        return Collections.unmodifiableList(members);
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
new file mode 100644
index 000000000..af83ffd74
--- /dev/null
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
@@ -0,0 +1,297 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public final class CodeGraphWriter
+{
+    private static final Comparator<CodeGraphSymbol> SYMBOL_COMPARATOR = new 
Comparator<CodeGraphSymbol>()
+    {
+        @Override
+        public int compare(CodeGraphSymbol first, CodeGraphSymbol second)
+        {
+            return first.getId().compareTo(second.getId());
+        }
+    };
+
+    public void write(CodeGraphModel model, Writer writer) throws IOException
+    {
+        writer.write("{\n");
+        writeProperty(writer, 1, "schemaVersion", 
CodeGraphModel.SCHEMA_VERSION, true);
+        writeProperty(writer, 1, "target", model.getTarget(), true);
+        writeProperty(writer, 1, "module", model.getModule(), true);
+        writeSymbols(writer, "symbols", model.getSymbols(), 1, true);
+        writeSymbols(writer, "externalSymbols", model.getExternalSymbols(), 1, 
false);
+        writer.write("}\n");
+    }
+
+    private void writeSymbols(Writer writer, String name, 
List<CodeGraphSymbol> symbols, int level, boolean comma) throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, name);
+        writer.write(": [");
+        List<CodeGraphSymbol> sortedSymbols = new 
ArrayList<CodeGraphSymbol>(symbols);
+        Collections.sort(sortedSymbols, SYMBOL_COMPARATOR);
+        if (!sortedSymbols.isEmpty())
+            writer.write('\n');
+        for (int i = 0; i < sortedSymbols.size(); i++)
+        {
+            writeSymbol(writer, sortedSymbols.get(i), level + 1);
+            if (i + 1 < sortedSymbols.size())
+                writer.write(',');
+            writer.write('\n');
+        }
+        if (!sortedSymbols.isEmpty())
+            indent(writer, level);
+        writer.write(']');
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeSymbol(Writer writer, CodeGraphSymbol symbol, int level) 
throws IOException
+    {
+        indent(writer, level);
+        writer.write("{\n");
+        writeProperty(writer, level + 1, "id", symbol.getId(), true);
+        writeProperty(writer, level + 1, "qualifiedName", 
symbol.getQualifiedName(), true);
+        writeProperty(writer, level + 1, "baseName", symbol.getBaseName(), 
true);
+        writeProperty(writer, level + 1, "package", symbol.getPackageName(), 
true);
+        int optionalPropertyCount = getOptionalPropertyCount(symbol);
+        writeProperty(writer, level + 1, "kind", symbol.getKind(), 
optionalPropertyCount > 0);
+        if (symbol.isExternal())
+            writeBooleanProperty(writer, level + 1, "external", true, 
--optionalPropertyCount > 0);
+        if (symbol.getSource() != null)
+            writeProperty(writer, level + 1, "source", 
symbol.getSource().replace('\\', '/'), --optionalPropertyCount > 0);
+        if (symbol.getDeclaringType() != null)
+            writeReferenceProperty(writer, level + 1, "declaringType", 
symbol.getDeclaringType(), --optionalPropertyCount > 0);
+        if (symbol.getType() != null)
+            writeReferenceProperty(writer, level + 1, "type", 
symbol.getType(), --optionalPropertyCount > 0);
+        if (symbol.getReturnType() != null)
+            writeReferenceProperty(writer, level + 1, "returnType", 
symbol.getReturnType(), --optionalPropertyCount > 0);
+        if (symbol.getBaseType() != null)
+            writeReferenceProperty(writer, level + 1, "baseType", 
symbol.getBaseType(), --optionalPropertyCount > 0);
+        if (!symbol.getInterfaces().isEmpty())
+        {
+            writeReferences(writer, "interfaces", symbol.getInterfaces(), 
level + 1, --optionalPropertyCount > 0);
+        }
+        if (!symbol.getParameters().isEmpty())
+        {
+            writeParameters(writer, symbol.getParameters(), level + 1, 
--optionalPropertyCount > 0);
+        }
+        if (!symbol.getMembers().isEmpty())
+            writeSymbols(writer, "members", symbol.getMembers(), level + 1, 
false);
+        indent(writer, level);
+        writer.write('}');
+    }
+
+    private int getOptionalPropertyCount(CodeGraphSymbol symbol)
+    {
+        int result = 0;
+        if (symbol.isExternal())
+            result++;
+        if (symbol.getSource() != null)
+            result++;
+        if (symbol.getDeclaringType() != null)
+            result++;
+        if (symbol.getType() != null)
+            result++;
+        if (symbol.getReturnType() != null)
+            result++;
+        if (symbol.getBaseType() != null)
+            result++;
+        if (!symbol.getInterfaces().isEmpty())
+            result++;
+        if (!symbol.getParameters().isEmpty())
+            result++;
+        if (!symbol.getMembers().isEmpty())
+            result++;
+        return result;
+    }
+
+    private void writeReferenceProperty(Writer writer, int level, String name, 
CodeGraphReference reference,
+            boolean comma) throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, name);
+        writer.write(": ");
+        writeReference(writer, reference, level);
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeReference(Writer writer, CodeGraphReference reference, 
int level) throws IOException
+    {
+        writer.write("{\n");
+        writeProperty(writer, level + 1, "id", reference.getId(), true);
+        writeProperty(writer, level + 1, "qualifiedName", 
reference.getQualifiedName(), true);
+        writeBooleanProperty(writer, level + 1, "external", 
reference.isExternal(), true);
+        writeBooleanProperty(writer, level + 1, "unresolved", 
reference.isUnresolved(), false);
+        indent(writer, level);
+        writer.write('}');
+    }
+
+    private void writeReferences(Writer writer, String name, 
List<CodeGraphReference> references, int level,
+            boolean comma) throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, name);
+        writer.write(": [\n");
+        for (int i = 0; i < references.size(); i++)
+        {
+            indent(writer, level + 1);
+            writeReference(writer, references.get(i), level + 1);
+            if (i + 1 < references.size())
+                writer.write(',');
+            writer.write('\n');
+        }
+        indent(writer, level);
+        writer.write(']');
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeParameters(Writer writer, List<CodeGraphParameter> 
parameters, int level, boolean comma)
+            throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, "parameters");
+        writer.write(": [\n");
+        for (int i = 0; i < parameters.size(); i++)
+        {
+            CodeGraphParameter parameter = parameters.get(i);
+            indent(writer, level + 1);
+            writer.write("{\n");
+            writeProperty(writer, level + 2, "name", parameter.getName(), 
true);
+            if (parameter.getType() == null)
+                writeProperty(writer, level + 2, "type", null, true);
+            else
+                writeReferenceProperty(writer, level + 2, "type", 
parameter.getType(), true);
+            writeBooleanProperty(writer, level + 2, "optional", 
parameter.isOptional(), true);
+            writeBooleanProperty(writer, level + 2, "rest", 
parameter.isRest(), true);
+            writeValueProperty(writer, level + 2, "defaultValue", 
parameter.getDefaultValue(), false);
+            indent(writer, level + 1);
+            writer.write('}');
+            if (i + 1 < parameters.size())
+                writer.write(',');
+            writer.write('\n');
+        }
+        indent(writer, level);
+        writer.write(']');
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeBooleanProperty(Writer writer, int level, String name, 
boolean value, boolean comma)
+            throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, name);
+        writer.write(value ? ": true" : ": false");
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeValueProperty(Writer writer, int level, String name, 
Object value, boolean comma)
+            throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, name);
+        writer.write(": ");
+        if (value == null)
+            writer.write("null");
+        else if (value instanceof Number || value instanceof Boolean)
+            writer.write(value.toString());
+        else
+            writeString(writer, value.toString());
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeProperty(Writer writer, int level, String name, String 
value, boolean comma) throws IOException
+    {
+        indent(writer, level);
+        writeString(writer, name);
+        writer.write(": ");
+        if (value == null)
+            writer.write("null");
+        else
+            writeString(writer, value);
+        if (comma)
+            writer.write(',');
+        writer.write('\n');
+    }
+
+    private void writeString(Writer writer, String value) throws IOException
+    {
+        writer.write('"');
+        for (int i = 0; i < value.length(); i++)
+        {
+            char character = value.charAt(i);
+            switch (character)
+            {
+                case '"':
+                    writer.write("\\\"");
+                    break;
+                case '\\':
+                    writer.write("\\\\");
+                    break;
+                case '\b':
+                    writer.write("\\b");
+                    break;
+                case '\f':
+                    writer.write("\\f");
+                    break;
+                case '\n':
+                    writer.write("\\n");
+                    break;
+                case '\r':
+                    writer.write("\\r");
+                    break;
+                case '\t':
+                    writer.write("\\t");
+                    break;
+                default:
+                    if (character < 0x20)
+                        writer.write(String.format("\\u%04x", (int)character));
+                    else
+                        writer.write(character);
+            }
+        }
+        writer.write('"');
+    }
+
+    private void indent(Writer writer, int level) throws IOException
+    {
+        for (int i = 0; i < level; i++)
+            writer.write("  ");
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphExporter.java
 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphExporter.java
new file mode 100644
index 000000000..15d4b0084
--- /dev/null
+++ 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphExporter.java
@@ -0,0 +1,111 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+
+import org.apache.royale.compiler.internal.test.ASTestBase;
+import org.apache.royale.compiler.tree.as.IClassNode;
+import org.junit.Test;
+
+public class TestCodeGraphExporter extends ASTestBase
+{
+    @Test
+    public void testClassAndPublicFieldAreCollectedSemantically()
+    {
+        IClassNode classNode = getClassNode("public class Widget {"
+                + "public var label:String;"
+                + "private var hidden:Number;"
+                + "}");
+
+        CodeGraphModel model = new CodeGraphExporter(project).export(
+                Collections.singleton(classNode.getDefinition()), "js", null);
+
+        assertEquals(1, model.getSymbols().size());
+        CodeGraphSymbol classSymbol = model.getSymbols().get(0);
+        assertEquals("as3://Widget", classSymbol.getId());
+        assertEquals("class", classSymbol.getKind());
+        assertNotNull(classSymbol.getBaseType());
+        assertEquals("Object", classSymbol.getBaseType().getQualifiedName());
+        assertTrue(classSymbol.getBaseType().isExternal());
+        assertEquals(1, classSymbol.getMembers().size());
+
+        CodeGraphSymbol fieldSymbol = classSymbol.getMembers().get(0);
+        assertEquals("as3://Widget#label", fieldSymbol.getId());
+        assertEquals("field", fieldSymbol.getKind());
+        assertEquals("Widget", 
fieldSymbol.getDeclaringType().getQualifiedName());
+        assertEquals("String", fieldSymbol.getType().getQualifiedName());
+        assertTrue(fieldSymbol.getType().isExternal());
+        assertEquals(2, model.getExternalSymbols().size());
+        assertTrue(model.getExternalSymbols().get(0).isExternal());
+        assertTrue(model.getExternalSymbols().get(1).isExternal());
+    }
+
+    @Test
+    public void testCallableMembersAreCollectedSemantically()
+    {
+        IClassNode classNode = getClassNode("public class Widget {"
+                + "public function Widget(value:String) {}"
+                + "public function get label():String { return null; }"
+                + "public function set label(value:String):void {}"
+                + "public function work(required:String, optional:Number = 2, 
...rest):Boolean { return true; }"
+                + "}");
+
+        CodeGraphModel model = new CodeGraphExporter(project).export(
+                Collections.singleton(classNode.getDefinition()), "js", null);
+        CodeGraphSymbol classSymbol = model.getSymbols().get(0);
+        assertEquals(4, classSymbol.getMembers().size());
+
+        CodeGraphSymbol constructor = findMember(classSymbol, "constructor");
+        assertEquals("as3://Widget#constructor(String)", constructor.getId());
+        assertEquals("String", 
constructor.getParameters().get(0).getType().getQualifiedName());
+
+        CodeGraphSymbol getter = findMember(classSymbol, "getter");
+        CodeGraphSymbol setter = findMember(classSymbol, "setter");
+        assertEquals("as3://Widget#label:get", getter.getId());
+        assertEquals("as3://Widget#label:set", setter.getId());
+        assertEquals("String", getter.getType().getQualifiedName());
+        assertEquals("String", setter.getType().getQualifiedName());
+
+        CodeGraphSymbol method = findMember(classSymbol, "method");
+        assertEquals("as3://Widget#work(String,Number,Array)", method.getId());
+        assertEquals("Boolean", method.getReturnType().getQualifiedName());
+        assertEquals(3, method.getParameters().size());
+        assertFalse(method.getParameters().get(0).isOptional());
+        assertTrue(method.getParameters().get(1).isOptional());
+        assertEquals(2, method.getParameters().get(1).getDefaultValue());
+        assertTrue(method.getParameters().get(2).isRest());
+    }
+
+    private CodeGraphSymbol findMember(CodeGraphSymbol owner, String kind)
+    {
+        for (CodeGraphSymbol member : owner.getMembers())
+        {
+            if (kind.equals(member.getKind()))
+                return member;
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphIdFactory.java
 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphIdFactory.java
new file mode 100644
index 000000000..8ec8cd714
--- /dev/null
+++ 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphIdFactory.java
@@ -0,0 +1,68 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.Test;
+
+public class TestCodeGraphIdFactory
+{
+    @Test
+    public void testDefinition()
+    {
+        assertEquals("as3://org/apache/royale/core/UIBase",
+                
CodeGraphIdFactory.definition("org.apache.royale.core.UIBase"));
+    }
+
+    @Test
+    public void testMember()
+    {
+        assertEquals("as3://org/apache/royale/core/UIBase#typeNames",
+                CodeGraphIdFactory.member("org.apache.royale.core.UIBase", 
"typeNames"));
+    }
+
+    @Test
+    public void testAccessors()
+    {
+        assertEquals("as3://org/apache/royale/core/UIBase#width:get",
+                CodeGraphIdFactory.accessor("org.apache.royale.core.UIBase", 
"width", true));
+        assertEquals("as3://org/apache/royale/core/UIBase#width:set",
+                CodeGraphIdFactory.accessor("org.apache.royale.core.UIBase", 
"width", false));
+    }
+
+    @Test
+    public void testCallable()
+    {
+        
assertEquals("as3://org/apache/royale/core/UIBase#setWidth(Number,Boolean)",
+                CodeGraphIdFactory.callable("org.apache.royale.core.UIBase", 
"setWidth",
+                        Arrays.asList("Number", "Boolean")));
+    }
+
+    @Test
+    public void testConstructor()
+    {
+        assertEquals("as3://org/apache/royale/core/UIBase#constructor()",
+                
CodeGraphIdFactory.constructor("org.apache.royale.core.UIBase", 
Collections.<String>emptyList()));
+    }
+}
\ No newline at end of file
diff --git 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
new file mode 100644
index 000000000..43cded64a
--- /dev/null
+++ 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
@@ -0,0 +1,97 @@
+/*
+ *
+ *  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.codegen.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import org.junit.Test;
+
+public class TestCodeGraphWriter
+{
+    @Test
+    public void testDeterministicDocument() throws IOException
+    {
+        CodeGraphModel model = new CodeGraphModel("js", null);
+        CodeGraphSymbol second = new CodeGraphSymbol("as3://example/Zed", 
"example.Zed", "Zed", "example", "class");
+        second.setSource("example\\Zed.as");
+        CodeGraphSymbol first = new CodeGraphSymbol("as3://example/Alpha", 
"example.Alpha", "Alpha", "example", "interface");
+        model.addSymbol(second);
+        model.addSymbol(first);
+
+        StringWriter output = new StringWriter();
+        new CodeGraphWriter().write(model, output);
+
+        assertEquals("{\n"
+                + "  \"schemaVersion\": \"1.0\",\n"
+                + "  \"target\": \"js\",\n"
+                + "  \"module\": null,\n"
+                + "  \"symbols\": [\n"
+                + "    {\n"
+                + "      \"id\": \"as3://example/Alpha\",\n"
+                + "      \"qualifiedName\": \"example.Alpha\",\n"
+                + "      \"baseName\": \"Alpha\",\n"
+                + "      \"package\": \"example\",\n"
+                + "      \"kind\": \"interface\"\n"
+                + "    },\n"
+                + "    {\n"
+                + "      \"id\": \"as3://example/Zed\",\n"
+                + "      \"qualifiedName\": \"example.Zed\",\n"
+                + "      \"baseName\": \"Zed\",\n"
+                + "      \"package\": \"example\",\n"
+                + "      \"kind\": \"class\",\n"
+                + "      \"source\": \"example/Zed.as\"\n"
+                + "    }\n"
+                + "  ],\n"
+                + "  \"externalSymbols\": []\n"
+                + "}\n", output.toString());
+    }
+
+    @Test
+    public void testSemanticDetails() throws IOException
+    {
+        CodeGraphModel model = new CodeGraphModel("swf", "example-module");
+        CodeGraphSymbol owner = new CodeGraphSymbol("as3://example/Widget", 
"example.Widget", "Widget", "example", "class");
+        owner.setBaseType(new CodeGraphReference("as3://Object", "Object", 
true, false));
+        CodeGraphSymbol method = new 
CodeGraphSymbol("as3://example/Widget#work(String)",
+                "example.Widget.work", "work", "example", "method");
+        method.setDeclaringType(new CodeGraphReference(owner.getId(), 
owner.getQualifiedName(), false, false));
+        method.setReturnType(new CodeGraphReference("as3://Boolean", 
"Boolean", true, false));
+        method.addParameter(new CodeGraphParameter("value",
+                new CodeGraphReference("as3://String", "String", true, false), 
false, false, null));
+        owner.addMember(method);
+        model.addSymbol(owner);
+
+        StringWriter firstOutput = new StringWriter();
+        StringWriter secondOutput = new StringWriter();
+        CodeGraphWriter writer = new CodeGraphWriter();
+        writer.write(model, firstOutput);
+        writer.write(model, secondOutput);
+
+        assertEquals(firstOutput.toString(), secondOutput.toString());
+        assertTrue(firstOutput.toString().contains("\"baseType\": {"));
+        assertTrue(firstOutput.toString().contains("\"members\": ["));
+        assertTrue(firstOutput.toString().contains("\"parameters\": ["));
+        assertTrue(firstOutput.toString().contains("\"external\": true"));
+    }
+}
\ No newline at end of file

Reply via email to