/** * Detect Unicode BOM in a source file. * * @param data Binary data of source file. * @param path The canonical path of source file. */ private void detectBOM(byte[] data, String path) { if (data.length > 2 && (byte) (data[0] ^ 0xEF) == 0 && (byte) (data[1] ^ 0xBB) == 0 && (byte) (data[2] ^ 0xBF) == 0) { App.exit("UTF8 BOM was found in " + path); } else if (data.length > 1 && (byte) (data[0] ^ 0xFE) == 0 && (byte) (data[1] ^ 0xFF) == 0) { App.exit("UTF16BE BOM was found in " + path); } else if (data.length > 1 && (byte) (data[0] ^ 0xFF) == 0 && (byte) (data[1] ^ 0xFE) == 0) { App.exit("UTF16LE BOM was found in " + path); } }
/** * Get dependencies of a source file. * * @param path The canonical path of source file. * @return Path of dependencies. */ private ArrayList<String> getDependencies(String path) { if (!dependenceMap.containsKey(path)) { ArrayList<String> dependencies = new ArrayList<String>(); Matcher m = PATTERN_REQUIRE.matcher(read(path, charset)); while (m.find()) { // Decide which root path to use. // Path wrapped in <> is related to root path. // Path wrapped in "" is related to parent folder of the source file. String root = null; if (m.group(1).equals("<")) { root = this.root; } else { root = new File(path).getParent(); } // Get path of required file. String required = m.group(2); File f = new File(root, required); if (f.exists()) { dependencies.add(canonize(f)); } else { App.exit("Cannot find required file " + required + " in " + path); } } dependenceMap.put(path, dependencies); } return dependenceMap.get(path); }
/** * Get the canonical path of a file. * * @param f The file. * @return The canonical path. */ private String canonize(File f) { String path = null; try { path = f.getCanonicalPath(); } catch (IOException e) { App.exit(e); } return path; }
/** * Get text content of a source file. * * @param path The canonical path of source file. * @param charset Source file encoding. * @return Source file content. */ public String read(String path, String charset) { String str = null; byte[] bin = read(path); try { str = Charset.forName(charset).newDecoder().decode(ByteBuffer.wrap(bin)).toString(); } catch (CharacterCodingException e) { App.exit("Cannot read " + path + " as " + charset + " encoded file"); } return str; }
/** * Get binary data of a source file. * * @param path The canonical path of source file. * @return Source file data. */ public byte[] read(String path) { if (!binaryCache.containsKey(path)) { try { BufferedInputStream bf = new BufferedInputStream(new FileInputStream(new File(path))); try { byte[] data = new byte[bf.available()]; bf.read(data); detectBOM(data, path); binaryCache.put(path, data); } finally { bf.close(); } } catch (IOException e) { App.exit(e); } } return binaryCache.get(path); }
/** * Locate root path. * * @param root The user-specified root path. */ private void locateRoot(String root) { // Locate default root folder. if (root == null) { File pwd = new File(".").getAbsoluteFile(); File f = pwd; String[] l = null; // Detect intl-style/xxx/htdocs by finding "js" and "css" in sub folders. do { f = f.getParentFile(); if (f == null) { break; } l = f.list( new FilenameFilter() { private Pattern pattern = Pattern.compile("^(?:js|css)$"); public boolean accept(File dir, String name) { return pattern.matcher(name).matches(); } }); } while (l.length != 2); // If present, use intl-style/xxx/htdocs as root folder for Alibaba. if (f != null) { this.root = canonize(f); // Else use present working folder as root folder. } else { this.root = canonize(pwd); } // Use user-specified root folder. } else { File f = new File(root); if (f.exists()) { this.root = canonize(f); } else { App.exit("The user-specified root folder " + root + " does not exist."); } } }
/** * Travel dependencies tree by DFS and Post-Order algorithm. * * @param tree The initial tree which contains root node only. * @param footprint The footprint of the traversal. * @param output Output queue of combined files. */ private void travel( Stack<ArrayList<String>> tree, Stack<String> footprint, ArrayList<String> output) { for (String node : tree.peek()) { // Detect circular dependences by looking back footprint. if (footprint.contains(node)) { String msg = "Circular dependences was found\n"; for (String path : footprint) { msg += " " + path + " ->\n"; } msg += " " + node; App.exit(msg); } // Skip visited node. if (output.contains(node)) { continue; } // Move forward. footprint.push(node); // Add sub nodes. tree.push(getDependencies(node)); // Travel sub nodes. travel(tree, footprint, output); // Clean visited nodes. tree.pop(); // Move backward. footprint.pop(); // Add first visited node to output queue. output.add(node); } }