blob: cf87fba8b1c87afcb40c0e62578f0d8707d1d3bc [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.compiler.internal.graph;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.royale.compiler.clients.problems.ProblemQuery;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.DependencyTypeSet;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogEmitterTokens;
import org.apache.royale.compiler.internal.driver.js.JSCompilationUnit;
import org.apache.royale.compiler.internal.driver.js.goog.JSGoogConfiguration;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.projects.DefinitionPriority;
import org.apache.royale.compiler.internal.projects.DependencyGraph;
import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
import org.apache.royale.compiler.problems.FileNotFoundProblem;
import org.apache.royale.compiler.problems.MainDefinitionQNameProblem;
import org.apache.royale.compiler.problems.UnexpectedExceptionProblem;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.swc.ISWC;
import org.apache.royale.swc.ISWCFileEntry;
import com.google.common.io.Files;
import com.google.debugging.sourcemap.FilePosition;
import com.google.debugging.sourcemap.SourceMapConsumerV3;
import com.google.debugging.sourcemap.SourceMapGeneratorV3;
import com.google.debugging.sourcemap.SourceMapParseException;
public class GoogDepsWriter {
public GoogDepsWriter(File outputFolder, String mainClassName, JSGoogConfiguration config, List<ISWC> swcs)
{
this.outputFolderPath = outputFolder.getAbsolutePath();
this.moduleOutput = config.getModuleOutput();
this.mainName = mainClassName;
removeCirculars = config.getRemoveCirculars();
sourceMaps = config.getSourceMap();
otherPaths = config.getSDKJSLib();
verbose = config.isVerbose();
otherPaths.add(new File(outputFolder.getParent(), "royale/Royale/src").getPath());
this.swcs = swcs;
if (verbose)
{
for (ISWC swc : swcs)
{
System.out.println("using SWC: " + swc.getSWCFile().getAbsolutePath());
}
}
}
private ProblemQuery problems;
private String moduleOutput;
private String outputFolderPath;
private String mainName;
private List<String> otherPaths;
private List<ISWC> swcs;
private boolean removeCirculars = false;
private boolean sourceMaps = false;
private boolean verbose = false;
private ArrayList<GoogDep> dps;
private DependencyGraph graph;
private CompilerProject project;
private ArrayList<String> staticInitializers;
private ArrayList<String> staticInitializerOwners;
private HashMap<String, GoogDep> depMap = new HashMap<String,GoogDep>();
private HashMap<String, ICompilationUnit> requireMap = new HashMap<String, ICompilationUnit>();
private HashMap<ICompilationUnit, String> requireMap2 = new HashMap<ICompilationUnit, String>();
public boolean needCSS = false;
public ArrayList<String> getListOfFiles(CompilerProject project, List<String> sourceExternFiles, ProblemQuery problems)
{
this.project = project;
this.problems = problems;
if (dps == null)
{
if (!buildDB())
return null;
dps = sort();
}
visited.clear();
ArrayList<String> files = new ArrayList<String>();
for (GoogDep gd : dps)
{
if (gd.fileInfo.isExtern)
sourceExternFiles.add(gd.filePath);
else
files.add(gd.filePath);
visited.put(gd.className, gd);
}
if (removeCirculars)
{
GoogDep mainDep = depMap.get(mainName);
visited.put(mainName, mainDep);
for (GoogDep gd : depMap.values())
{
if (!visited.containsKey(gd.className))
{
if (gd.fileInfo.isExtern)
sourceExternFiles.add(gd.filePath);
else
files.add(gd.filePath);
}
}
files.add(mainDep.filePath);
}
return files;
}
public String generateDeps(CompilerProject project, ProblemQuery problems) throws FileNotFoundException
{
this.project = project;
this.problems = problems;
if (dps == null)
{
if (!buildDB())
return "";
dps = sort();
}
ArrayList<String> usedDeps = new ArrayList<String>();
ArrayList<String> addedDeps = new ArrayList<String>();
StringBuilder sb = new StringBuilder();
int n = dps.size();
for (int i = n - 1; i >= 0; i--)
{
GoogDep gd = dps.get(i);
if (!isGoogClass(gd.className))
{
addedDeps.add(gd.filePath);
if (removeCirculars)
{
ArrayList <String> deps = new ArrayList<String>();
computeDeps(deps, gd, usedDeps);
sb.append("goog.addDependency('")
.append(relativePath(gd.filePath))
.append("', ['")
.append(gd.className)
.append("'], [");
appendDependencies(deps, sb);
sb.append("]);\n");
}
else
{
sb.append("goog.addDependency('")
.append(relativePath(gd.filePath))
.append("', ['")
.append(gd.className)
.append("'], [");
appendDependencies(gd.deps, sb);
sb.append("]);\n");
}
}
}
if (removeCirculars)
{
StringBuilder mainDeps = new StringBuilder();
GoogDep mainDep = depMap.get(mainName);
mainDeps.append("goog.addDependency('").append(relativePath(mainDep.filePath)).append("', ['")
.append(mainDep.className).append("'], [");
ArrayList<String> restOfDeps = new ArrayList<String>();
for (String dep: mainDep.deps)
{
if (isGoogProvided(dep))
{
restOfDeps.add(dep);
}
}
if (mainDep.fileInfo.impls != null)
{
for (String dep: mainDep.fileInfo.impls)
{
if (isGoogProvided(dep))
{
restOfDeps.add(dep);
}
}
}
DependencyTypeSet dependencyTypes = DependencyTypeSet.allOf();
// get the list of all units not referenced by other units
for (GoogDep gd : depMap.values())
{
if (usedDeps.contains(gd.className))
continue;
if (gd.className.equals(mainName))
{
if (gd.fileInfo.impls != null)
{
for (String d : gd.fileInfo.impls)
{
if (!restOfDeps.contains(d) && !gd.fileInfo.isExtern && isGoogProvided(d) && !usedDeps.contains(d))
{
restOfDeps.add(d);
}
}
}
continue;
}
ICompilationUnit unit = requireMap.get(gd.className);
if (unit == null)
{
if (!restOfDeps.contains(gd.className) && !gd.fileInfo.isExtern && isGoogProvided(gd.className) && !usedDeps.contains(gd.className))
{
restOfDeps.add(gd.className);
}
continue;
}
Set<ICompilationUnit> deps = graph.getDirectReverseDependencies(unit, dependencyTypes);
if (deps.size() == 0)
{
if (!restOfDeps.contains(gd.className) && !gd.fileInfo.isExtern && isGoogProvided(gd.className) && !usedDeps.contains(gd.className))
{
restOfDeps.add(gd.className);
}
}
}
appendDependencies(restOfDeps, mainDeps);
mainDeps.append("]);\n");
sb.insert(0, mainDeps);
sb.insert(0, "// generated by Royale\n");
for (String dep : restOfDeps)
{
GoogDep gd = depMap.get(dep);
if (gd == null)
{
//added this to prevent a NullPointerException when the
//GoogDep is null. -JT
problems.add(new FileNotFoundProblem(dep));
continue;
}
if (addedDeps.contains(gd.filePath))
continue;
ArrayList<String> deps = new ArrayList<String>();
computeDeps(deps, gd, usedDeps);
sb.append("goog.addDependency('")
.append(relativePath(gd.filePath))
.append("', ['")
.append(gd.className)
.append("'], [");
appendDependencies(deps, sb);
sb.append("]);\n");
}
addRestOfDeps(mainDep, restOfDeps);
}
return sb.toString();
}
private void computeDeps(ArrayList<String> deps, GoogDep gd, ArrayList<String> usedDeps) {
if (gd.fileInfo.impls != null)
{
deps.addAll(gd.fileInfo.impls);
for (String dep : gd.fileInfo.impls)
if (!usedDeps.contains(dep))
usedDeps.add(dep);
}
if (gd.fileInfo.staticDeps != null)
{
for (String dep : gd.fileInfo.staticDeps)
{
if (!deps.contains(dep))
deps.add(dep);
if (!usedDeps.contains(dep))
usedDeps.add(dep);
}
}
}
private boolean isGoogClass(String className)
{
return className.startsWith("goog.");
}
private boolean buildDB()
{
staticInitializers = new ArrayList<String>();
staticInitializerOwners = new ArrayList<String>();
graph = new DependencyGraph();
if (isGoogClass(mainName))
{
problems.add(new MainDefinitionQNameProblem("Google Closure Library", mainName));
return false;
}
if (!isGoogProvided(mainName))
{
problems.add(new MainDefinitionQNameProblem("External Libraries", mainName));
return false;
}
addDeps(mainName);
return true;
}
private HashMap<String, GoogDep> visited = new HashMap<String, GoogDep>();
private ArrayList<GoogDep> sort()
{
// first, promote all dependencies of classes used in static initializers to
// the level of static dependencies since their constructors will be
// run early. This may need to be a full transitive walk someday.
// This is because we move many goog.requires from the various classes
// to the main app when removing circulars. We only keep goog.requires
// that Closure Compiler cares about, which are requires for @extends
// and @implements. However, as classes required by the application get
// loaded, their static initializers will fail if we haven't already goog.required
// the classes used by these static initializers, so we have to keep goog.requires
// used by these static initializers in the class files.
int n = staticInitializers.size();
for (int i = 0; i < n; i++)
{
String staticClass = staticInitializers.get(i);
String staticOwner = staticInitializerOwners.get(i);
GoogDep info = depMap.get(staticClass);
GoogDep ownerInfo = depMap.get(staticOwner);
if (info != null && info.fileInfo != null && info.fileInfo.deps != null &&
ownerInfo != null && ownerInfo.fileInfo != null)
{
if (ownerInfo.fileInfo.staticDeps == null)
ownerInfo.fileInfo.staticDeps = new ArrayList<String>();
for (String dep : info.fileInfo.deps)
{
if (!ownerInfo.fileInfo.staticDeps.contains(dep)
&& !isGoogClass(dep)
&& !dep.equals(staticOwner))
{
ownerInfo.fileInfo.staticDeps.add(dep);
if (verbose)
{
System.out.println(staticClass + " used in static initializer of " + staticOwner + " so make " + dep + " a static dependency");
}
// all things added here should get added to graph in sortFunction
}
}
}
}
ArrayList<GoogDep> arr = new ArrayList<GoogDep>();
GoogDep current = depMap.get(mainName);
sortFunction(current, arr);
if (removeCirculars)
{
ICompilationUnit mainUnit = requireMap.get(mainName);
ArrayList<ICompilationUnit> roots = new ArrayList<ICompilationUnit>();
roots.add(mainUnit);
requireMap.remove(mainName);
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.GOOG_DEPS) == CompilerDiagnosticsConstants.GOOG_DEPS)
{
Collection<ICompilationUnit> units = graph.getCompilationUnits();
System.out.println("Contents of graph:");
for (ICompilationUnit unit : units)
{
try {
System.out.println(unit.getQualifiedNames().toString());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
List<ICompilationUnit> order = graph.topologicalSort(requireMap.values());
if (graph.lastCircularDependencyException != null)
{
problems.add(new UnexpectedExceptionProblem(graph.lastCircularDependencyException));
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.GOOG_DEPS) == CompilerDiagnosticsConstants.GOOG_DEPS)
{
System.out.println("Contents of graph in order:");
for (ICompilationUnit unit : order)
{
try {
System.out.println(unit.getQualifiedNames().toString());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ArrayList<GoogDep> depsInOrder = new ArrayList<GoogDep>();
for (ICompilationUnit unit : order)
{
String name = requireMap2.get(unit);
if (isGoogClass(name)) continue;
GoogDep dep = depMap.get(name);
if (dep == null)
{
if (isGoogProvided(name))
{
System.out.println("No GoogDep for " + name);
//added this to prevent a NullPointerException when the
//GoogDep is null. -JT
problems.add(new FileNotFoundProblem(name));
continue;
}
}
else
depsInOrder.add(dep);
}
return depsInOrder;
}
return arr;
}
private void sortFunction(GoogDep current, List<GoogDep> arr)
{
visited.put(current.className, current);
if (removeCirculars)
{
removeRequires(current);
}
if (verbose)
{
System.out.println("Dependencies calculated for '" + current.className + "'");
}
ICompilationUnit unit = null;
if (removeCirculars)
{
unit = requireMap.get(current.className);
if (unit == null)
{
unit = new JSCompilationUnit(project, current.filePath, DefinitionPriority.BasePriority.SOURCE_LIST, current.className);
graph.addCompilationUnit(unit);
requireMap.put(current.className, unit);
requireMap2.put(unit, current.className);
}
if (current.fileInfo.deps == null)
return;
}
ArrayList<String> impls = current.fileInfo.impls != null ? current.fileInfo.impls : null;
if (impls != null)
{
for (String className : impls)
{
if (!isGoogClass(className))
{
GoogDep gd = depMap.get(className);
if (gd != null)
{
if (removeCirculars)
{
ICompilationUnit base = requireMap.get(className);
if (base == null)
{
base = new JSCompilationUnit(project, className, DefinitionPriority.BasePriority.SOURCE_LIST, className);
graph.addCompilationUnit(base);
requireMap.put(className, base);
requireMap2.put(base, className);
}
if (verbose)
{
System.out.println(current.className + " depends on " + className);
}
graph.addDependency(unit, base, DependencyType.INHERITANCE);
}
if (!visited.containsKey(className))
{
sortFunction(gd, arr);
}
}
}
}
}
if (removeCirculars)
{
if (current.fileInfo.staticDeps != null && removeCirculars)
{
for (String staticDep : current.fileInfo.staticDeps)
{
ICompilationUnit base = requireMap.get(staticDep);
if (base == null)
{
base = new JSCompilationUnit(project, staticDep, DefinitionPriority.BasePriority.SOURCE_LIST, staticDep);
graph.addCompilationUnit(base);
requireMap.put(staticDep, base);
requireMap2.put(base, staticDep);
}
if (verbose)
{
System.out.println(current.className + " static initialization depends on " + staticDep);
}
graph.addDependency(unit, base, DependencyType.INHERITANCE);
}
}
}
ArrayList<String> deps = current.deps;
for (String className : deps)
{
if (!isGoogClass(className) && isGoogProvided(className))
{
GoogDep gd = depMap.get(className);
if (gd == null)
{
System.out.println("No GoogDep for " + className);
throw new RuntimeException("Unable to find dependency information for class: " + className);
}
if (!visited.containsKey(className))
{
sortFunction(gd, arr);
}
}
}
arr.add(current);
}
private void addRestOfDeps(GoogDep main, List<String> restOfDeps)
{
List<String> fileLines;
try {
File mainFile = new File(main.filePath);
fileLines = Files.readLines(mainFile, Charset.defaultCharset());
SourceMapConsumerV3 sourceMapConsumer = null;
File sourceMapFile = null;
if (sourceMaps)
{
sourceMapFile = new File(main.filePath + ".map");
if (sourceMapFile.exists())
{
String sourceMapContents = FileUtils.readFileToString(sourceMapFile);
sourceMapConsumer = new SourceMapConsumerV3();
try
{
sourceMapConsumer.parse(sourceMapContents);
}
catch(SourceMapParseException e)
{
sourceMapConsumer = null;
}
}
}
// first scan requires in case this is a module and some have been externed
int j = main.fileInfo.googProvideLine + 1;
while (j < fileLines.size() && !fileLines.get(j).contains(JSGoogEmitterTokens.GOOG_REQUIRE.getToken()))
{
j++;
}
while (j < fileLines.size() && fileLines.get(j).contains(JSGoogEmitterTokens.GOOG_REQUIRE.getToken()))
{
String line = fileLines.get(j);
int c = line.indexOf(JSGoogEmitterTokens.GOOG_REQUIRE.getToken());
int c2 = line.indexOf(")");
String s = line.substring(c + 14, c2 - 1);
if (!isGoogProvided(s))
{
fileLines.remove(j);
sourceMapConsumer = removeLineFromSourceMap(sourceMapConsumer, mainFile.getName(), j);
}
else
{
j++;
}
}
int n = restOfDeps.size();
for (int i = n - 1; i >= 0; i--)
{
String dep = restOfDeps.get(i);
StringBuilder lineBuilder = new StringBuilder();
lineBuilder.append(JSGoogEmitterTokens.GOOG_REQUIRE.getToken())
.append("('")
.append(dep)
.append("');");
fileLines.add(main.fileInfo.googProvideLine + 1, lineBuilder.toString());
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, mainFile.getName(), main.fileInfo.googProvideLine + 1);
}
PrintWriter out = new PrintWriter(new FileWriter(mainFile));
for (String s : fileLines)
{
out.println(s);
}
out.close();
if (sourceMapConsumer != null)
{
String newSourceMap = sourceMapConsumerToString(sourceMapConsumer, mainFile.getName());
PrintWriter sourceMapOut = new PrintWriter(new FileWriter(sourceMapFile));
sourceMapOut.print(newSourceMap);
sourceMapOut.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void addDeps(String className)
{
if (depMap.containsKey(className) || isGoogClass(className) || !isGoogProvided(className))
{
return;
}
// build goog dependency list
GoogDep gd = new GoogDep();
gd.className = className;
gd.filePath = getFilePath(className);
if(gd.filePath.isEmpty()) {
// TODO Send a ICompilerProblem instead.
throw new RuntimeException("Unable to find JavaScript filePath for class: " + className);
}
depMap.put(gd.className, gd);
List<String> fileLines;
try {
fileLines = Files.readLines(new File(gd.filePath), Charset.defaultCharset());
FileInfo fi = getFileInfo(fileLines, className);
gd.fileInfo = fi;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (gd.fileInfo.impls != null)
{
for (String dep : gd.fileInfo.impls)
{
if (gd.fileInfo.provides != null &&
gd.fileInfo.provides.contains(dep)) continue;
addDeps(dep);
}
}
gd.deps = new ArrayList<String>();
if (gd.fileInfo.deps != null)
{
for (String dep : gd.fileInfo.deps)
{
gd.deps.add(dep);
if (gd.fileInfo.provides != null &&
gd.fileInfo.provides.contains(dep)) continue;
addDeps(dep);
}
if (gd.fileInfo.staticDeps != null)
{
for (String dep : gd.fileInfo.staticDeps)
{
if (!gd.deps.contains(dep))
{
gd.deps.add(dep);
if (gd.fileInfo.provides != null &&
gd.fileInfo.provides.contains(dep)) continue;
addDeps(dep);
}
}
}
}
}
void removeRequires(GoogDep gd)
{
String className = gd.className;
// remove requires that are not base classes and interfaces
try
{
gd = depMap.get(className);
File depFile = new File(gd.filePath);
List<String> fileLines = Files.readLines(depFile, Charset.defaultCharset());
ArrayList<String> finalLines = new ArrayList<String>();
SourceMapConsumerV3 sourceMapConsumer = null;
File sourceMapFile = null;
if (sourceMaps)
{
sourceMapFile = new File(gd.filePath + ".map");
if (sourceMapFile.exists())
{
String sourceMapContents = FileUtils.readFileToString(sourceMapFile);
sourceMapConsumer = new SourceMapConsumerV3();
try
{
sourceMapConsumer.parse(sourceMapContents);
}
catch(SourceMapParseException e)
{
sourceMapConsumer = null;
}
}
}
boolean firstDependency = true;
StringBuilder sb = new StringBuilder();
sb.append(JSGoogEmitterTokens.ROYALE_DEPENDENCY_LIST.getToken());
ArrayList<String> writtenRequires = new ArrayList<String>();
// int staticDepsLine = -1;
int lastRequireLine = -1;
FileInfo fi = gd.fileInfo;
// int suppressCount = 0;
int i = 0;
int stopLine = fi.constructorLine;
if (fi.constructorLine == -1) // standalone functions
stopLine = fi.googProvideLine + 4; // search a few more lines after goog.provide
for (String line : fileLines)
{
if (i < stopLine)
{
int c = line.indexOf(JSGoogEmitterTokens.ROYALE_DEPENDENCY_LIST.getToken());
if (c > -1)
return; // already been processed
// c = line.indexOf(JSGoogEmitterTokens.ROYALE_STATIC_DEPENDENCY_LIST.getToken());
// if (c > -1)
// staticDepsLine = i;
c = line.indexOf(JSGoogEmitterTokens.GOOG_REQUIRE.getToken());
if (c > -1)
{
lastRequireLine = i;
int c2 = line.indexOf(")");
String s = line.substring(c + 14, c2 - 1);
if (((gd.fileInfo.impls == null || !gd.fileInfo.impls.contains(s)) &&
(gd.fileInfo.staticDeps == null || !gd.fileInfo.staticDeps.contains(s))) ||
!isGoogProvided(s))
{
// don't remove the require if some class needs it at static initialization
// time
// suppressCount++;
if (verbose)
{
System.out.println(gd.filePath + " removing require: " + s);
}
if (!firstDependency)
sb.append(",");
sb.append(s);
firstDependency = false;
sourceMapConsumer = removeLineFromSourceMap(sourceMapConsumer, depFile.getName(), finalLines.size());
continue;
}
else
{
writtenRequires.add(s);
}
}
}
finalLines.add(line);
//no need to call addLineToSourceMap here because we're
//populating finalLines for the first time
i++;
}
// add any static deps not already listed that were added by static initializers;
if (gd.fileInfo.staticDeps != null)
{
if (lastRequireLine == -1)
lastRequireLine = gd.fileInfo.googProvideLine + 1;
for (String dep : gd.fileInfo.staticDeps)
{
if (!writtenRequires.contains(dep) && isGoogProvided(dep))
{
StringBuilder lineBuilder = new StringBuilder();
lineBuilder.append(JSGoogEmitterTokens.GOOG_REQUIRE.getToken())
.append("('")
.append(dep)
.append("');");
finalLines.add(lastRequireLine++, lineBuilder.toString());
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, new File(gd.filePath).getName(), lastRequireLine);
if (verbose)
{
System.out.println("adding require for static dependency " + dep + " to " + className);
}
}
}
}
//if (suppressCount > 0)
//{
if (fi.suppressLine > 0)
{
if (fi.suppressLine < fi.constructorLine || fi.constructorLine == -1)
{
String line = finalLines.get(fi.suppressLine);
int c = line.indexOf("@suppress {");
if (c > -1)
{
if (!line.contains("missingRequire"))
{
line = line.substring(0, c) + "@suppress {missingRequire|" + line.substring(c + 11);
finalLines.remove(fi.suppressLine);
finalLines.add(fi.suppressLine, line);
//no need to call addLineToSourceMap or removeLineFromSourceMap here
}
}
else
System.out.println("Confused by @suppress in " + className);
}
else
{
// the @suppress was for the constructor or some other thing so add a top-level
// @suppress
if (fi.fileoverviewLine > -1)
{
// there is already a fileOverview but no @suppress
finalLines.add(fi.fileoverviewLine + 1, " * @suppress {missingRequire}");
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.fileoverviewLine + 1);
}
else if (fi.googProvideLine > -1)
{
finalLines.add(fi.googProvideLine, " */");
finalLines.add(fi.googProvideLine, " * @suppress {missingRequire}");
finalLines.add(fi.googProvideLine, " * @fileoverview");
finalLines.add(fi.googProvideLine, "/**");
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
}
else
{
System.out.println("Confused by @suppress in " + className);
}
}
}
else
{
if (fi.fileoverviewLine > -1)
{
// there is already a fileoverview but no @suppress
finalLines.add(fi.fileoverviewLine + 1, " * @suppress {missingRequire}");
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.fileoverviewLine + 1);
}
else if (fi.googProvideLine > -1)
{
finalLines.add(fi.googProvideLine, " */");
finalLines.add(fi.googProvideLine, " * @suppress {missingRequire}");
finalLines.add(fi.googProvideLine, " * @fileoverview");
finalLines.add(fi.googProvideLine, "/**");
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), fi.googProvideLine);
}
else
{
System.out.println("Confused by @suppress in " + className);
}
}
//}
sb.append("*/");
finalLines.add(gd.fileInfo.googProvideLine + 1, sb.toString());
sourceMapConsumer = addLineToSourceMap(sourceMapConsumer, depFile.getName(), gd.fileInfo.googProvideLine + 1);
PrintWriter out = new PrintWriter(new FileWriter(depFile));
for (String s : finalLines)
{
out.println(s);
}
out.close();
if (sourceMapConsumer != null)
{
String newSourceMap = sourceMapConsumerToString(sourceMapConsumer, depFile.getName());
PrintWriter sourceMapOut = new PrintWriter(new FileWriter(sourceMapFile));
sourceMapOut.print(newSourceMap);
sourceMapOut.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
String sourceMapConsumerToString(SourceMapConsumerV3 consumer, String file)
{
SourceMapGeneratorV3 generator = sourceMapConsumerToGenerator(consumer);
StringBuilder builder = new StringBuilder();
try
{
generator.appendTo(builder, file);
}
catch(IOException e)
{
return "";
}
return builder.toString();
}
private void appendExtraMappingToGenerator(SourceMapGeneratorV3 generator,
String sourceName,
String symbolName,
FilePosition sourceStartPosition,
FilePosition startPosition,
FilePosition endPosition)
{
//add an extra mapping because there seems to be a bug in
//SourceMapGeneratorV3's appendTo() that omits the last
//entry, for some reason
FilePosition newEndPosition = new FilePosition(endPosition.getLine(), endPosition.getColumn() + 1);
generator.addMapping(sourceName, null, sourceStartPosition, endPosition, newEndPosition);
}
private SourceMapGeneratorV3 sourceMapConsumerToGenerator(SourceMapConsumerV3 consumer)
{
final SourceMapGeneratorV3 generator = new SourceMapGeneratorV3();
final SourceMapEntryCounter counter = new SourceMapEntryCounter();
generator.setSourceRoot(consumer.getSourceRoot());
consumer.visitMappings(counter);
consumer.visitMappings(new SourceMapConsumerV3.EntryVisitor()
{
private int index = 0;
@Override
public void visit(String sourceName,
String symbolName,
FilePosition sourceStartPosition,
FilePosition startPosition,
FilePosition endPosition) {
generator.addMapping(sourceName, symbolName, sourceStartPosition, startPosition, endPosition);
index++;
if(index == counter.count)
{
//add an extra mapping because there seems to be a bug in
//SourceMapGeneratorV3's appendTo() that omits the last
//entry, for some reason
appendExtraMappingToGenerator(generator, sourceName, symbolName, sourceStartPosition, startPosition, endPosition);
}
}
});
return generator;
}
class SourceMapEntryCounter implements SourceMapConsumerV3.EntryVisitor
{
private int count = 0;
@Override
public void visit(String sourceName,
String symbolName,
FilePosition sourceStartPosition,
FilePosition startPosition,
FilePosition endPosition) {
count++;
}
}
SourceMapConsumerV3 sourceMapGeneratorToConsumer(SourceMapGeneratorV3 generator, String fileName)
{
StringBuilder builder = new StringBuilder();
try
{
generator.appendTo(builder, fileName);
}
catch(IOException e)
{
return null;
}
SourceMapConsumerV3 consumer = new SourceMapConsumerV3();
try
{
consumer.parse(builder.toString());
}
catch(SourceMapParseException e)
{
return null;
}
return consumer;
}
SourceMapConsumerV3 addLineToSourceMap(SourceMapConsumerV3 consumer, String sourceFileName, final int lineToAdd)
{
if (consumer == null)
{
return null;
}
final SourceMapGeneratorV3 generator = new SourceMapGeneratorV3();
final SourceMapEntryCounter counter = new SourceMapEntryCounter();
generator.setSourceRoot(consumer.getSourceRoot());
consumer.visitMappings(counter);
consumer.visitMappings(new SourceMapConsumerV3.EntryVisitor()
{
private int index = 0;
@Override
public void visit(String sourceName,
String symbolName,
FilePosition sourceStartPosition,
FilePosition startPosition,
FilePosition endPosition) {
if(startPosition.getLine() >= lineToAdd)
{
startPosition = new FilePosition(startPosition.getLine() + 1, startPosition.getColumn());
endPosition = new FilePosition(endPosition.getLine() + 1, endPosition.getColumn());
}
generator.addMapping(sourceName, symbolName, sourceStartPosition, startPosition, endPosition);
index++;
if(index == counter.count)
{
//add an extra mapping because there seems to be a bug in
//SourceMapGeneratorV3's appendTo() that omits the last
//entry, for some reason
appendExtraMappingToGenerator(generator, sourceName, symbolName, sourceStartPosition, startPosition, endPosition);
}
}
});
return sourceMapGeneratorToConsumer(generator, sourceFileName);
}
SourceMapConsumerV3 removeLineFromSourceMap(SourceMapConsumerV3 consumer, String sourceFileName, final int lineToRemove)
{
if (consumer == null)
{
return null;
}
final SourceMapGeneratorV3 generator = new SourceMapGeneratorV3();
final SourceMapEntryCounter counter = new SourceMapEntryCounter();
generator.setSourceRoot(consumer.getSourceRoot());
consumer.visitMappings(counter);
consumer.visitMappings(new SourceMapConsumerV3.EntryVisitor()
{
private int index = 0;
@Override
public void visit(String sourceName,
String symbolName,
FilePosition sourceStartPosition,
FilePosition startPosition,
FilePosition endPosition) {
if(startPosition.getLine() == lineToRemove)
{
return;
}
if(startPosition.getLine() > lineToRemove)
{
startPosition = new FilePosition(startPosition.getLine() - 1, startPosition.getColumn());
}
if(endPosition.getLine() > lineToRemove)
{
endPosition = new FilePosition(endPosition.getLine() - 1, endPosition.getColumn());
}
generator.addMapping(sourceName, symbolName, sourceStartPosition, startPosition, endPosition);
index++;
if(index == counter.count)
{
//add an extra mapping because there seems to be a bug in
//SourceMapGeneratorV3's appendTo() that omits the last
//entry, for some reason
appendExtraMappingToGenerator(generator, sourceName, symbolName, sourceStartPosition, startPosition, endPosition);
}
}
});
return sourceMapGeneratorToConsumer(generator, sourceFileName);
}
FileInfo getFileInfo(List<String> lines, String className)
{
FileInfo fi = new FileInfo();
int numProvides = 0;
int constructorCount = 0;
int n = lines.size();
fi.constructorLine = -1;
fi.suppressLine = -1;
fi.fileoverviewLine = -1;
fi.googProvideLine = -1;
for (int i = 0; i < n; i++)
{
String line = lines.get(i);
int c2;
int c = line.indexOf("*/");
if (c > -1 && constructorCount > 0 && constructorCount == numProvides)
{
break;
}
else
{
c = line.indexOf("@constructor");
if (c > -1)
{
if (fi.constructorLine == -1)
fi.constructorLine = i;
constructorCount++;
}
else
{
c = line.indexOf("@interface");
if (c > -1)
fi.constructorLine = i;
else
{
c = line.indexOf("@suppress");
if (c > -1)
fi.suppressLine = i;
else
{
c = line.indexOf("@fileoverview");
if (c > -1)
fi.fileoverviewLine = i;
else
{
c = line.indexOf("goog.provide");
if (c > -1)
{
if (fi.googProvideLine == -1)
fi.googProvideLine = i;
if (numProvides > 0)
{
if (fi.provides == null)
fi.provides = new ArrayList<String>();
c2 = line.indexOf(")", c);
String provide = line.substring(c + 14, c2 - 1);
fi.provides.add(provide);
}
numProvides++;
}
else
{
c = line.indexOf("@implements");
if (c > -1)
{
if (fi.impls == null)
fi.impls = new ArrayList<String>();
c2 = line.indexOf("}", c);
String impl = line.substring(c + 13, c2);
if (!fi.impls.contains(impl) && !impl.contentEquals(className))
fi.impls.add(impl);
if (impl.equals("org.apache.royale.core.ICSSImpl"))
needCSS = true;
}
else
{
c = line.indexOf("@extends");
if (c > -1)
{
if (fi.impls == null)
fi.impls = new ArrayList<String>();
c2 = line.indexOf("}", c);
String impl = line.substring(c + 10, c2);
if (!fi.impls.contains(impl) && !impl.contentEquals(className))
fi.impls.add(impl);
}
else
{
String token = JSGoogEmitterTokens.ROYALE_STATIC_DEPENDENCY_LIST.getToken();
c = line.indexOf(token);
if (c > -1)
{
c2 = line.indexOf("*/");
line = line.substring(c + token.length(), c2);
List<String> staticDeps = Arrays.asList(line.split(","));
fi.staticDeps = new ArrayList<String>();
fi.staticDeps.addAll(staticDeps);
for (String staticDep : staticDeps)
{
if (staticDep.equals(className))
continue;
staticInitializers.add(staticDep);
staticInitializerOwners.add(className);
}
}
else
{
c = line.indexOf("@externs");
if (c > -1)
{
fi.isExtern = true;
}
else
{
token = JSGoogEmitterTokens.ROYALE_DEPENDENCY_LIST.getToken();
c = line.indexOf(token);
if (c > -1)
{
c2 = line.indexOf("*/");
line = line.substring(c + token.length(), c2);
fi.deps = new ArrayList<String>();
if (line.length() > 2) // don't add blank or space if no deps
fi.deps.addAll(Arrays.asList(line.split(",")));
// fi.depsLine = i;
}
else /* if (fi.depsLine == 0) */
{
token = JSGoogEmitterTokens.GOOG_REQUIRE.getToken();
c = line.indexOf(token);
if (c > -1)
{
c2 = line.indexOf(")");
String s = line.substring(c + 14, c2 - 1);
if (fi.deps == null)
fi.deps = new ArrayList<String>();
fi.deps.add(s);
}
}
}
}
}
}
}
}
}
}
}
}
}
if (fi.deps != null)
{
Collections.sort(fi.deps);
}
if (fi.staticDeps != null)
{
Collections.sort(fi.staticDeps);
}
return fi;
}
String getFilePath(String className)
{
String fn;
File destFile;
File f;
String classPath = className.replace(".", File.separator);
fn = outputFolderPath + File.separator + classPath + ".js";
f = new File(fn);
if (f.exists())
{
return fn;
}
for (String otherPath : otherPaths)
{
fn = otherPath + File.separator + classPath + ".js";
f = new File(fn);
if (f.exists())
{
fn = outputFolderPath + File.separator + classPath + ".js";
destFile = new File(fn);
// copy source to output
try {
FileUtils.copyFile(f, destFile);
// (erikdebruin) copy class assets files
if (className.contains("org.apache.royale"))
{
File assetsDir = new File(f.getParentFile(), "assets");
if (assetsDir.exists())
{
String nameOfClass = className.substring(className.lastIndexOf('_') + 1);
File[] assetsList = assetsDir.listFiles();
assert assetsList != null;
for (File assetFile : assetsList) {
String assetFileName = assetFile.getName();
if (assetFile.isFile() && assetFileName.indexOf(nameOfClass) == 0) {
String pathOfClass;
pathOfClass = className.substring(0, className.lastIndexOf('_'));
pathOfClass = pathOfClass.replace(".", File.separator);
destFile = new File(outputFolderPath +
File.separator + pathOfClass +
File.separator + "assets" +
File.separator + assetFileName);
FileUtils.copyFile(assetFile, destFile);
destFile = new File(outputFolderPath.replace("js-debug", "js-release") +
File.separator + pathOfClass +
File.separator + "assets" +
File.separator + assetFileName);
FileUtils.copyFile(assetFile, destFile);
if (verbose)
{
System.out.println("Copied assets of the '" + nameOfClass + "' class");
}
}
}
}
}
} catch (IOException e) {
System.out.println("Error copying file for class: " + className);
}
return fn;
}
}
for (ISWC swc : swcs)
{
ISWCFileEntry fileEntry = getFileEntry(swc, className);
if (fileEntry != null)
{
fn = outputFolderPath + File.separator + classPath + ".js";
destFile = new File(fn);
// copy source to output
try {
InputStream inStream = fileEntry.createInputStream();
OutputStream outStream = FileUtils.openOutputStream(destFile);
byte[] b = new byte[1024 * 1024];
int bytes_read;
while ((bytes_read = inStream.read(b)) != -1)
{
outStream.write(b, 0, bytes_read);
}
outStream.flush();
outStream.close();
inStream.close();
//if source maps requested, copy from the swc, if available
if (sourceMaps)
{
ISWCFileEntry sourceMapFileEntry = getFileEntry(swc, className, ".js.map");
if (sourceMapFileEntry != null)
{
String sourceMapFn = outputFolderPath + File.separator + classPath + ".js.map";
File sourceMapDestFile = new File(sourceMapFn);
inStream = sourceMapFileEntry.createInputStream();
outStream = FileUtils.openOutputStream(sourceMapDestFile);
b = new byte[1024 * 1024];
while ((bytes_read = inStream.read(b)) != -1)
{
outStream.write(b, 0, bytes_read);
}
outStream.flush();
outStream.close();
inStream.close();
}
}
// (erikdebruin) copy class assets files
if (className.contains("org.apache.royale"))
{
Map<String, ISWCFileEntry> includedfiles = swc.getFiles();
Set<String> includedList = includedfiles.keySet();
for (String included : includedList)
{
if (included.contains(".png") ||
included.contains(".gif") ||
included.contains(".jpg") ||
included.contains(".jpeg") ||
included.contains(".svg") ||
included.contains(".json"))
{
fileEntry = includedfiles.get(included);
String assetName = outputFolderPath + File.separator + included;
File assetFile = new File(assetName);
inStream = fileEntry.createInputStream();
outStream = FileUtils.openOutputStream(assetFile);
b = new byte[inStream.available()];
inStream.read(b);
outStream.write(b);
inStream.close();
outStream.flush();
outStream.close();
if (verbose)
{
System.out.println("Copied asset " + assetName);
}
}
}
}
} catch (IOException e) {
System.out.println("Error copying file for class: " + className);
}
return fn;
}
}
System.out.println("Could not find file for class: " + className);
problems.add(new FileNotFoundProblem(className));
return "";
}
private ISWCFileEntry getFileEntry(ISWC swc, String className)
{
return getFileEntry(swc, className, ".js");
}
private ISWCFileEntry getFileEntry(ISWC swc, String className, String extension)
{
String fwdClassPath = className.replace(".", "/");
String bckClassPath = className.replace(".", "\\");
ISWCFileEntry fileEntry = swc.getFile("js/src/" + fwdClassPath + extension);
if (fileEntry == null)
fileEntry = swc.getFile("js/out/" + fwdClassPath + extension);
if (fileEntry == null)
fileEntry = swc.getFile("js/src/" + bckClassPath + extension);
if (fileEntry == null)
fileEntry = swc.getFile("js/out/" + bckClassPath + extension);
if (fileEntry == null)
fileEntry = swc.getFile("js\\src\\" + bckClassPath + extension);
if (fileEntry == null)
fileEntry = swc.getFile("js\\out\\" + bckClassPath + extension);
return fileEntry;
}
/*
private ArrayList<String> getDirectDependencies(String fn)
{
ArrayList<String> deps = new ArrayList<String>();
FileInputStream fis;
try {
fis = new FileInputStream(fn);
Scanner scanner = new Scanner(fis, "UTF-8");
boolean inInjectHTML = false;
while (scanner.hasNextLine())
{
String s = scanner.nextLine();
if (s.contains("goog.inherits"))
break;
if (inInjectHTML)
{
int c = s.indexOf("</inject_html>");
if (c > -1)
{
inInjectHTML = false;
continue;
}
}
if (inInjectHTML)
{
s = s.trim();
if (s.startsWith("*"))
s = s.substring(1);
additionalHTML.add(s);
continue;
}
else
otherScanning(s);
int c = s.indexOf(JSGoogEmitterTokens.GOOG_REQUIRE.getToken());
if (c > -1)
{
int c2 = s.indexOf(")");
s = s.substring(c + 14, c2 - 1);
deps.add(s);
}
c = s.indexOf("<inject_html>");
if (c > -1)
{
inInjectHTML = true;
}
}
scanner.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return deps;
}
*/
protected void otherScanning(String s)
{
}
private void appendDependencies(ArrayList<String> deps, StringBuilder builder)
{
boolean hasDeps = false;
for (String dep : deps)
{
if (hasDeps)
{
builder.append(", ");
}
builder.append("'");
builder.append(dep);
builder.append("'");
hasDeps = true;
}
}
String relativePath(String path)
{
String replacement = "../../..";
if (path.indexOf(outputFolderPath) == 0)
{
if (moduleOutput != null)
{
replacement += moduleOutput;
if (replacement.endsWith("/"))
replacement = replacement.substring(0, replacement.length() - 1);
}
path = path.replace(outputFolderPath, replacement);
}
else
{
for (String otherPath : otherPaths)
{
if (path.indexOf(otherPath) == 0)
{
path = path.replace(otherPath, "../../..");
}
}
}
// paths are actually URIs and always have forward slashes
path = path.replace('\\', '/');
return path;
}
boolean isGoogProvided(String className)
{
return ((RoyaleJSProject)project).isGoogProvided(className);
}
boolean isExternal(String className)
{
ICompilationUnit cu = project.resolveQNameToCompilationUnit(className);
if (cu == null) return false; // unit testing
return ((RoyaleJSProject)project).isExternalLinkage(cu);
}
private class GoogDep
{
public String filePath;
public String className;
public ArrayList<String> deps;
public FileInfo fileInfo;
}
private class FileInfo
{
public ArrayList<String> impls;
public ArrayList<String> deps;
public ArrayList<String> staticDeps;
public ArrayList<String> provides;
public int constructorLine;
// public int depsLine;
public int suppressLine;
public int fileoverviewLine;
public int googProvideLine;
public boolean isExtern;
}
}