/*
*
* 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.flex.compiler.internal.graph;
import java.io.File;
import java.io.FileInputStream;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.flex.compiler.clients.problems.ProblemQuery;
import org.apache.flex.compiler.internal.codegen.js.goog.JSGoogEmitterTokens;
import org.apache.flex.compiler.internal.driver.js.goog.JSGoogConfiguration;
import org.apache.flex.compiler.problems.FileNotFoundProblem;
import org.apache.flex.swc.ISWC;
import org.apache.flex.swc.ISWCFileEntry;
import com.google.common.io.Files;
public class GoogDepsWriter {
public GoogDepsWriter(File outputFolder, String mainClassName, JSGoogConfiguration config, List<ISWC> swcs)
{
this.outputFolderPath = outputFolder.getAbsolutePath();
this.mainName = mainClassName;
removeCirculars = config.getRemoveCirculars();
otherPaths = config.getSDKJSLib();
otherPaths.add(new File(outputFolder.getParent(), "flexjs/FlexJS/src").getPath());
this.swcs = swcs;
for (ISWC swc : swcs)
{
System.out.println("using SWC: " + swc.getSWCFile().getAbsolutePath());
}
}
private ProblemQuery problems;
private String outputFolderPath;
private String mainName;
private List<String> otherPaths;
private List<ISWC> swcs;
private boolean removeCirculars = false;
private boolean problemsFound = false;
private ArrayList<GoogDep> dps;
private HashMap<String,GoogDep> depMap = new HashMap<String,GoogDep>();
private HashMap<String, String> requireMap = new HashMap<String, String>();
public boolean needCSS = false;
public ArrayList<String> getListOfFiles(ProblemQuery problems)
{
problemsFound = false;
this.problems = problems;
if (dps == null)
{
buildDB();
dps = sort(mainName);
}
ArrayList<String> files = new ArrayList<String>();
for (GoogDep gd : dps)
{
files.add(gd.filePath);
}
return files;
}
public String generateDeps(ProblemQuery problems) throws FileNotFoundException
{
problemsFound = false;
this.problems = problems;
if (dps == null)
{
buildDB();
dps = sort(mainName);
}
StringBuilder sb = new StringBuilder("// generated by FalconJX\n");
int n = dps.size();
for (int i = n - 1; i >= 0; i--)
{
GoogDep gd = dps.get(i);
if (!isGoogClass(gd.className))
{
sb.append("goog.addDependency('").append(relativePath(gd.filePath)).append("', ['")
.append(gd.className).append("'], [").append(getDependencies(gd.deps)).append("]);\n");
}
}
return sb.toString();
}
private boolean isGoogClass(String className)
{
return className.startsWith("goog.");
}
private void buildDB()
{
addDeps(mainName);
}
public ArrayList<String> filePathsInOrder = new ArrayList<String>();
public ArrayList<String> additionalHTML = new ArrayList<String>();
private HashMap<String, GoogDep> visited = new HashMap<String, GoogDep>();
private HashMap<String, GoogDep> inArray = new HashMap<String, GoogDep>();
private ArrayList<GoogDep> sort(String rootClassName)
{
ArrayList<GoogDep> arr = new ArrayList<GoogDep>();
GoogDep current = depMap.get(rootClassName);
sortFunction(current, arr);
return arr;
}
private void sortFunction(GoogDep current, ArrayList<GoogDep> arr)
{
visited.put(current.className, current);
filePathsInOrder.add(current.filePath);
if (removeCirculars)
removeCirculars(current);
System.out.println("Dependencies calculated for '" + current.filePath + "'");
ArrayList<GoogDep> visitedButNotAdded = new ArrayList<GoogDep>();
ArrayList<String> deps = current.deps;
for (String className : deps)
{
if (!isGoogClass(className))
{
if (!visited.containsKey(className))
{
GoogDep gd = depMap.get(className);
sortFunction(gd, arr);
}
else
{
if (!inArray.containsKey(className))
{
// if we get here, we haven't yet added the dependency to the
// array of GoogDeps because we are computing its dependencies.
// For example, class A extends B which references class C which
// extends B. This isn't a circular reference. But B needs to
// be in the array before C instead of just before A.
visitedButNotAdded.add(depMap.get(className));
System.out.println("Visited but haven't added: '" + className + "'");
}
}
}
}
for (GoogDep gdep : visitedButNotAdded)
{
if (!inArray.containsKey(gdep.className))
{
arr.add(gdep);
inArray.put(gdep.className, gdep);
}
}
if (!inArray.containsKey(current.className))
{
arr.add(current);
inArray.put(current.className, current);
}
}
private void addDeps(String className)
{
if (depMap.containsKey(className) || isGoogClass(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();
}
ArrayList<String> deps = getDirectDependencies(gd.filePath);
gd.deps = new ArrayList<String>();
for (String dep : deps)
{
if (gd.fileInfo.impls != null && gd.fileInfo.impls.contains(dep))
{
if (!requireMap.containsKey(dep))
{
// we are first class that needs this dependency
// at prototype initialization time
requireMap.put(dep, className);
}
}
else if (depMap.containsKey(dep) && !isGoogClass(dep))
{
continue;
}
gd.deps.add(dep);
}
for (String dep : deps)
{
addDeps(dep);
}
}
void removeCirculars(GoogDep gd)
{
String className = gd.className;
// remove requires that would cause circularity
try
{
List<String> fileLines = Files.readLines(new File(gd.filePath), Charset.defaultCharset());
ArrayList<String> finalLines = new ArrayList<String>();
FileInfo fi = gd.fileInfo;
int suppressCount = 0;
int i = 0;
for (String line : fileLines)
{
if (i < fi.constructorLine)
{
int c = line.indexOf(JSGoogEmitterTokens.GOOG_REQUIRE.getToken());
if (c > -1)
{
int c2 = line.indexOf(")");
String s = line.substring(c + 14, c2 - 1);
if (gd.fileInfo.impls == null || !gd.fileInfo.impls.contains(s))
{
if (requireMap.containsKey(s) && requireMap.get(s) != className)
{
// don't add the require if some class needs it at static initialization
// time and that class is not this class
suppressCount++;
System.out.println(gd.filePath + " removing circular (static): " + s);
continue;
}
else if (!gd.deps.contains(s))
{
// someone require'd this class
suppressCount++;
System.out.println(gd.filePath + " removing circular: " + s);
continue;
}
else if (gd.deps.contains(s) && !isGoogClass(s) && onProtoChain(s, className))
{
// if we are on the proto chain of s, then take away our require of s
suppressCount++;
System.out.println(gd.filePath + " removing circular (proto): " + s);
continue;
}
}
}
}
finalLines.add(line);
i++;
}
if (suppressCount > 0)
{
if (fi.suppressLine > 0)
{
if (fi.suppressLine < fi.constructorLine)
{
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);
}
}
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}");
}
else if (fi.googProvideLine > -1)
{
finalLines.add(fi.googProvideLine, " */");
finalLines.add(fi.googProvideLine, " * @suppress {missingRequire}");
finalLines.add(fi.googProvideLine, " * @fileoverview");
finalLines.add(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}");
}
else if (fi.googProvideLine > -1)
{
finalLines.add(fi.googProvideLine, " */");
finalLines.add(fi.googProvideLine, " * @suppress {missingRequire}");
finalLines.add(fi.googProvideLine, " * @fileoverview");
finalLines.add(fi.googProvideLine, "/**");
}
else
{
System.out.println("Confused by @suppress in " + className);
}
}
}
File file = new File(gd.filePath);
PrintWriter out = new PrintWriter(new FileWriter(file));
for (String s : finalLines)
{
out.println(s);
}
out.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
boolean onProtoChain(String name, String base)
{
GoogDep gd = depMap.get(name);
if (gd == null)
{
System.out.println("no dep info for " + name);
return false;
}
if (gd.fileInfo.impls != null)
{
if (gd.fileInfo.impls.contains(base))
return true;
for (String s : gd.fileInfo.impls)
{
if (!isGoogClass(s) && onProtoChain(s, base))
return true;
}
}
return false;
}
FileInfo getFileInfo(List<String> lines, String className)
{
FileInfo fi = new FileInfo();
int n = lines.size();
fi.constructorLine = n;
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("goog.inherits");
if (c > -1)
{
String inheritLine = "";
while (true)
{
inheritLine += line;
c2 = line.indexOf(")");
if (c2 > -1)
break;
else
{
i++;
line = lines.get(i);
}
}
c = inheritLine.indexOf(",");
c2 = inheritLine.indexOf(")");
fi.inherits = inheritLine.substring(c + 1, c2).trim();
return fi;
}
else
{
c = line.indexOf("@constructor");
if (c > -1)
fi.constructorLine = i;
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)
fi.googProvideLine = i;
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);
fi.impls.add(impl);
if (impl.equals("org.apache.flex.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);
fi.impls.add(impl);
}
}
}
}
}
}
}
}
}
return fi;
}
String getFilePath(String className)
{
String fn;
File destFile;
File f;
String classPath = className.replace(".", File.separator);
// special case app names with underscores, but hope that
// no other class names have underscores in them
if (className.equals(mainName))
classPath = className;
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.flex"))
{
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);
System.out.println("Copied assets of the '" + nameOfClass + "' class");
}
}
}
}
} catch (IOException e) {
System.out.println("Error copying file for class: " + className);
}
return fn;
}
}
String fwdClassPath = className.replace(".", "/");
String bckClassPath = className.replace(".", "\\");
for (ISWC swc : swcs)
{
ISWCFileEntry fileEntry = swc.getFile("js/src/" + fwdClassPath + ".js");
if (fileEntry == null)
fileEntry = swc.getFile("js/out/" + fwdClassPath + ".js");
if (fileEntry == null)
fileEntry = swc.getFile("js/src/" + bckClassPath + ".js");
if (fileEntry == null)
fileEntry = swc.getFile("js/out/" + bckClassPath + ".js");
if (fileEntry == null)
fileEntry = swc.getFile("js\\src\\" + bckClassPath + ".js");
if (fileEntry == null)
fileEntry = swc.getFile("js\\out\\" + bckClassPath + ".js");
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();
// (erikdebruin) copy class assets files
if (className.contains("org.apache.flex"))
{
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();
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));
problemsFound = true;
return "";
}
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;
}
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;
}
private String getDependencies(ArrayList<String> deps)
{
String s = "";
for (String dep : deps)
{
if (s.length() > 0)
{
s += ", ";
}
s += "'" + dep + "'";
}
return s;
}
String relativePath(String path)
{
if (path.indexOf(outputFolderPath) == 0)
{
path = path.replace(outputFolderPath, "../../..");
}
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;
}
private class GoogDep
{
public String filePath;
public String className;
public ArrayList<String> deps;
public FileInfo fileInfo;
}
@SuppressWarnings( "unused" )
private class FileInfo
{
public String inherits;
public ArrayList<String> impls;
public int constructorLine;
public int suppressLine;
public int fileoverviewLine;
public int googProvideLine;
}
}