/* vim: set ts=2 et sw=2 cindent fo=qroca: */
package com.globant.katari.jsmodule.domain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.globant.katari.jsmodule.view.ResourceSets;
import com.globant.katari.jsmodule.view.ResourceSet;
/** Looks for the dep file corresponding to the given javascript file path.
*
* It then fetches for the dependencies files included in that dep and returns
* them in a list.
*
* @author ivan.bedecarats@globant.com
*/
public class DependenciesFinder {
/** The class logger.
*/
private static Logger log = LoggerFactory.getLogger(DependenciesFinder.class);
/** The resource sets, never null.
*/
private ResourceSets resourceSets = new ResourceSets();
/** True if the application is running in debug mode, false otherwise.
*/
private boolean debugMode;
/** Constructs a new {@link DependenciesFinder}.
*
* @param theDebugMode true if the application is running in debug mode,
* false otherwise.
*/
public DependenciesFinder(final boolean theDebugMode) {
debugMode = theDebugMode;
}
/** Fetches the immediate dependencies of the given file.
*
* If the .dep.js is empty, i.e., it doesn't have any sort of information, a
* {@link RuntimeException} will be thrown. The same will happen if the file
* couldn't be parsed or if a {@link JSONArray} named "js" couldn't be
* obtained from it.
*
* @param fileToBeResolved The path of the file whose dependencies are going
* to be fetched. Cannot be null nor empty.
*
* @return The list of paths of the dependencies of the given file. Never
* returns null. Returns an empty {@link List} if no dependencies are found or
* if the given file doesn't exist (because we don't support .dep.js files
* with empty {@link JSONArray} for .js with no dependencies).
*/
public List<String> find(final String fileToBeResolved) {
Validate.notEmpty(fileToBeResolved, "The file cannot be null nor empty");
List<String> foundDependencies = new ArrayList<String>();
String fileName = getPathWithDepJsExtension(fileToBeResolved);
InputStream fileContent = null;
if (debugMode) {
ResourceSet resourceSet = resourceSets.find(fileName);
if (resourceSet != null) {
String filePath = buildPath(resourceSet.getDebugPrefix(), fileName);
log.debug("In debug mode, looking for file {}", filePath);
File file = new File(filePath);
if (file.exists()) {
log.debug("Found {} in debug mode.", filePath);
try {
fileContent = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException ("Error opening file " + filePath, e);
}
}
}
if (fileContent == null) {
fileContent = getClass().getResourceAsStream(fileName);
}
} else {
fileContent = getClass().getResourceAsStream(fileName);
}
// if the file was found that means it has dependencies files. Otherwise,
// it hasn't.
if (fileContent != null) {
String jsonTxt;
try {
jsonTxt = IOUtils.toString(fileContent);
} catch (IOException e) {
throw new RuntimeException("Error reading dep file " + fileName, e);
} finally {
IOUtils.closeQuietly(fileContent);
}
JSONObject fileDependency;
try {
fileDependency = new JSONObject(jsonTxt);
} catch (JSONException e) {
throw new RuntimeException("Error parsing dep file " + fileName, e);
}
JSONArray fileDependencyFiles;
try {
fileDependencyFiles = fileDependency.getJSONArray("js");
for (int i = 0; i < fileDependencyFiles.length(); i++) {
foundDependencies.add(fileDependencyFiles.getString(i));
}
} catch (JSONException e) {
throw new RuntimeException(
"Error obtaning js dependencies files from dep file " + fileName, e);
}
}
return foundDependencies;
}
/** Returns the file with the .dep.js depency extension.
* @param file The file path of the original js file. It's never null nor
* empty. It should be a valid path for a javascript file, i.e., it should end
* with the extension .js.
* @return The file path ending with the extension .dep.js. It's never null.
*/
private String getPathWithDepJsExtension(final String file) {
Validate.notEmpty(file, "The file cannot be empty null nor empty.");
Validate.isTrue(file.endsWith(".js"),
"The file must have a javascript extension.");
int fileExtensionDot = file.lastIndexOf(".");
String fileWithoutExtension = file.substring(0, fileExtensionDot);
return fileWithoutExtension + ".dep.js";
}
/** Concatenates two path names.
*
* This is protected as an aid for subclasses.
*
* @param prefix The first component of the file name. It cannot be null.
*
* @param name The second component of the file name. It cannot be null.
*
* @return A file name of the form prefix/name with the correct number of /.
*/
private String buildPath(final String prefix, final String name) {
Validate.notNull(prefix, "The file component prefix cannot be null.");
Validate.notNull(name, "The second file component cannot be null.");
if (prefix.endsWith("/") && name.startsWith("/")) {
return prefix + name.substring(1);
} else if (prefix.endsWith("/") || name.startsWith("/")) {
return prefix + name;
}
return prefix + "/" + name;
}
}