All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jlgrock.javascriptframework.closurecompiler.CalcDeps Maven / Gradle / Ivy

package com.github.jlgrock.javascriptframework.closurecompiler;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import com.github.jlgrock.javascriptframework.mavenutils.io.DirectoryIO;

/**
 * Represents a dependency that is used to build and walk a tree. This is a
 * direct port from the google python script.
 * 
 */
public final class CalcDeps {

	/**
	 * Private Constructor for Utility Class.
	 */
	private CalcDeps() {
	}

	/**
	 * The Logger.
	 */
	private static final Logger LOGGER = Logger.getLogger(CalcDeps.class);

	/**
	 * Build a list of dependencies from a list of files. Takes a list of files,
	 * extracts their provides and requires, and builds out a list of dependency
	 * objects.
	 * 
	 * @param googleBaseJS
	 *            the location of base.js in the google library
	 * @param files
	 *            a list of files to be parsed for goog.provides and
	 *            goog.requires.
	 * @return A list of dependency objects, one for each file in the files
	 *         argument.
	 * @throws IOException
	 *             if there is a problem parsing the files for dependency info
	 */
	private static HashMap buildDependenciesFromFiles(
			final File googleBaseJS, final Collection files)
			throws IOException {
		HashMap result = new HashMap();
		Set searchedAlready = new HashSet();
		for (File file : files) {
			if (!searchedAlready.contains(file) && !file.equals(googleBaseJS)) {
				DependencyInfo dep = AnnotationFileReader
						.parseForDependencyInfo(file);
				result.put(file, dep);
				searchedAlready.add(file);
			}
		}
		return result;
	}

	/**
	 * Calculates the dependencies for given inputs.
	 * 
	 * This method takes a list of paths (files, directories) and builds a
	 * searchable data structure based on the namespaces that each .js file
	 * provides. It then parses through each input, resolving dependencies
	 * against this data structure. The final output is a list of files,
	 * including the inputs, that represent all of the code that is needed to
	 * compile the given inputs.
	 * 
	 * @param baseJs
	 *            the base.js file that is in the closure library
	 * @param paths
	 *            the references (files, directories) that are used to build the
	 *            dependency hash.
	 * @param inputs
	 *            the inputs (files, directories, namespaces) that have
	 *            dependencies that need to be calculated.
	 * @return A list of all files, including inputs, that are needed to compile
	 *         the given inputs.
	 * @throws IOException
	 *             if there is a problem parsing the files
	 */
	private static List calculateDependencies(
			final File baseJs, final Collection inputs,
			final Collection paths) throws IOException {
		HashSet temp = new HashSet();
		temp.addAll(inputs);
		HashMap inputHash = buildDependenciesFromFiles(
				baseJs, inputs);
		HashMap searchHash = buildDependenciesFromFiles(
				baseJs, paths);
		LOGGER.info("Dependencies Calculated.");

		List sortedDeps = slowSort(inputHash.values(),
				searchHash.values());
		LOGGER.info("Dependencies Sorted.");

		return sortedDeps;
	}

	/**
	 * Print out a deps.js file from a list of source paths.
	 * 
	 * @param googleBaseFile
	 *            the location of base.js in the google library
	 * @param sortedDeps
	 *            The sorted list of dependencies
	 * @param outputFile
	 *            The output file.
	 * @throws IOException
	 *             if there is a problem writing the file
	 * @return True on success, false if it was unable to find the base path to
	 *         generate deps relative to.
	 */
	private static boolean outputDeps(final File googleBaseFile,
			final Collection sortedDeps, final File outputFile)
			throws IOException {
		DirectoryIO.createDir(outputFile.getParentFile());
		FileWriter fw = new FileWriter(outputFile);
		BufferedWriter buff = new BufferedWriter(fw);

		buff.append("\n// This file was autogenerated by CalcDeps.java\n");
		for (DependencyInfo fileDep : sortedDeps) {
			if (fileDep != null) {
				buff.write(fileDep.toDepsString(googleBaseFile));
				buff.write("\n");
				buff.flush();
			}
		}
		LOGGER.info("Deps file written.");

		return true;

	}

	/**
	 * Print out a requires.js file from a list of source paths.
	 * 
	 * @param googleBaseFile
	 *            the location of base.js in the google library
	 * @param sortedDeps
	 *            The sorted list of dependencies
	 * @param outputFile
	 *            The output file.
	 * @throws IOException
	 *             if there is a problem writing the file
	 * @return True on success, false if it was unable to find the base path to
	 *         generate deps relative to.
	 */
	private static boolean outputRequires(final File googleBaseFile,
			final Collection sortedDeps, final File outputFile)
			throws IOException {
		DirectoryIO.createDir(outputFile.getParentFile());
		FileWriter fw = new FileWriter(outputFile);
		BufferedWriter buff = new BufferedWriter(fw);

		buff.append("\n// This file was autogenerated by CalcDeps.java\n");
		for (DependencyInfo fileDep : sortedDeps) {
			if (fileDep != null) {
				buff.write(fileDep.toRequiresString(googleBaseFile));
				buff.flush();
			}
		}
		LOGGER.info("Deps file written.");

		return true;

	}

	/**
	 * Compare every element to one another. This is significantly slower than a
	 * merge sort, but guarantees that deps end up in the right order
	 * 
	 * @param inputs
	 *            the inputs to scan
	 * @param deps
	 *            the external dependencies
	 * @return the list of dependencyInfo objects
	 */
	private static List slowSort(
			final Collection inputs,
			final Collection deps) {
		HashMap searchSet = buildSearchList(deps);
		HashSet seenList = new HashSet();
		ArrayList resultList = new ArrayList();
		for (DependencyInfo input : inputs) {
			if (!seenList.contains(input.getFile())) {
				seenList.add(input.getFile());
				for (String require : input.getRequires()) {
					orderDependenciesForNamespace(input.getFile(), require,
							searchSet, seenList, resultList);
				}
				resultList.add(input);
			}
		}
		return resultList;
	}

	/**
	 * Will order the Dependencies for a single namespace.
	 * 
	 * @param inputFile
	 *            the file
	 * @param requireNamespace
	 *            whether or not to require the namespace
	 * @param searchSet
	 *            the set to search through
	 * @param seenList
	 *            the list of objects that have been seen already (which may be
	 *            added to)
	 * @param resultList
	 *            the resulting list which will be added to
	 */
	private static void orderDependenciesForNamespace(final File inputFile,
			final String requireNamespace,
			final HashMap searchSet,
			final HashSet seenList,
			final ArrayList resultList) {
		if (!searchSet.containsKey(requireNamespace)) {
			LOGGER.error("Problem with require in file '" + inputFile.getAbsolutePath()
					+ "'. Found goog.require for '" + requireNamespace
					+ "', but did not find a corresponding goog.provide.");
		}
		DependencyInfo dep = searchSet.get(requireNamespace);
		if (!seenList.contains(dep.getFile())) {
			seenList.add(dep.getFile());
			for (String subRequire : dep.getRequires()) {
				orderDependenciesForNamespace(dep.getFile(), subRequire,
						searchSet, seenList, resultList);
			}
			resultList.add(dep);
		}
	}

	/**
	 * Build the search list for seraching for dependencies.
	 * 
	 * @param deps
	 *            the external dependencies
	 * @return the list of external dependencies, hashed by the namespace
	 */
	private static HashMap buildSearchList(
			final Collection deps) {
		HashMap returnVal = new HashMap();
		for (DependencyInfo dep : deps) {
			for (String provide : dep.getProvides()) {
				returnVal.put(provide, dep);
			}
		}
		return returnVal;
	}

	/**
	 * convert the sortedDependency list into a list of files.
	 * 
	 * @param sortedDeps
	 *            the sorted dependencies
	 * @return the sorted list of files
	 */
	private static List pullFilesFromDeps(
			final List sortedDeps) {
		ArrayList returnVal = new ArrayList();
		for (DependencyInfo dep : sortedDeps) {
			returnVal.add(dep.getFile());
		}
		return returnVal;
	}

	/**
	 * This will sort the list of dependencies, write a dependency file, and
	 * return the list of dependencies.
	 * 
	 * @param googleBaseFile
	 *            the base.js file that is in the google closure library
	 * @param inputs
	 *            the set of input files to parse for provides and requires
	 * @param paths
	 *            to additional resources that will have provides and requires
	 * @param depsFile
	 *            the deps file
	 * @param requiresFile
	 *            the requires file
	 * @return the list of calculated dependencies, just in case it is needed
	 * @throws IOException
	 *             if there is a problem reading from any dependencies or
	 *             writing the depenency file
	 */
	public static List executeCalcDeps(final File googleBaseFile,
			final Collection inputs, final Collection paths,
			final File depsFile, final File requiresFile) throws IOException {
		LOGGER.debug("Finding Closure dependencies...");
		List sortedDeps = calculateDependencies(googleBaseFile,
				inputs, paths);

		// create deps file
		LOGGER.debug("Outputting Closure dependency file...");
		outputDeps(googleBaseFile, sortedDeps, depsFile);

		// create deps file
		if (requiresFile != null) {
			LOGGER.debug("Outputting Closure dependency requires file...");
			outputRequires(googleBaseFile, sortedDeps, requiresFile);
		}

		LOGGER.debug("Closure dependencies created");
		return pullFilesFromDeps(sortedDeps);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy