/* * * 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; } }