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

org.duelengine.merge.BuildManager Maven / Gradle / Ivy

package org.duelengine.merge;

import java.io.*;
import java.security.*;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BuildManager {

	private static final String HASH_ALGORITHM = "SHA-1";
	private static final String NEWLINE = System.getProperty("line.separator");
	private static final String PROPERTY_LIST_DELIM = "|";
	private static final Logger log = LoggerFactory.getLogger(BuildManager.class);

	private final Map hashLookup = new LinkedHashMap();
	private final Map> dependencyMap = new LinkedHashMap>();
	private final Map> childLinkMap = new LinkedHashMap>();
	private final Map compactors;
	private final Settings settings;
	private final Stack dependencyChain = new Stack();

	/**
	 * @param settings path location settings
	 */
	public BuildManager(Settings settings) {
		this(settings,
			new MergeCompactor(
				new JSPlaceholderGenerator(),
				new CSSPlaceholderGenerator()),
			new NullCompactor(settings.getExtensions()),
			new CSSCompactor(),
			new JSCompactor());
	}

	/**
	 * @param settings path location settings
	 * @param compactors list of all active compactors
	 */
	public BuildManager(Settings settings, Compactor... compactors) {
		if (settings == null) {
			throw new NullPointerException("settings");
		}
		if (compactors == null) {
			throw new NullPointerException("compactors");
		}
		if (settings.getSourceDir() == null || !settings.getSourceDir().exists()) {
			throw new IllegalArgumentException("Missing source directory "+settings.getSourceDir());
		}

		this.settings = settings;

		this.compactors = new LinkedHashMap(compactors.length);
		for (Compactor compactor : compactors) {
			for (String ext : compactor.getSourceExtensions()) {
				this.compactors.put(ext, compactor);
			}
		}
	}

	/**
	 * Compiles merge files and processes resources
	 * @throws IOException 
	 * @throws NoSuchAlgorithmException 
	 */
	public void execute()
			throws IOException, NoSuchAlgorithmException {

		Map inputFiles = findFiles();

		for (File source : inputFiles.keySet()) {
			processResource(
				inputFiles.get(source),
				source);
		}

		writeCompactionMap();
		writeChildLinksMap();
	}

	public boolean isProcessed(String path) {
		return hashLookup.containsKey(path);
	}

	public String getProcessedPath(String path) {
		return hashLookup.get(path);
	}

	public String getPlaceholderPath(String path) {
		String hashed = hashLookup.get(path);
		if (hashed == null || hashed.isEmpty()) {
			return path;
		}

		hashed = hashLookup.get(hashed);
		if (hashed == null || hashed.isEmpty()) {
			return path;
		}

		return hashed;
	}

	public void setProcessedPath(String path, String hashPath) {
		hashLookup.put(path, hashPath);
	}

	private void removeProcessedPath(String path) {
		hashLookup.remove(path);
	}

	public void ensureProcessed(String path) {

		if (isProcessed(path) && getTargetFile(path).exists()) {
			return;
		}

		try {
			processResource(path, settings.findSourceFile(path));

		} catch (NoSuchAlgorithmException e) {
			log.error(e.getMessage(), e);

		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
	}
	
	private void processResource(String path, File source)
			throws IOException, NoSuchAlgorithmException {

		// keep track of currently compacting paths to prevent cycles
		if (dependencyChain.contains(path)) {
			log.error("Cyclical dependencies detected in: "+path);
			return;
		}
		dependencyChain.push(path);

		try {
			String sourceExt = getExtension( source.getCanonicalPath() );
			Compactor compactor = compactors.get(sourceExt);
			if (compactor == null) {
				log.error("No compactor registered for "+sourceExt);
				return;
			}
	
			File target;
			if (isProcessed(path)) {
				target = getTargetFile(path);

			} else {
				MessageDigest hash = MessageDigest.getInstance(HASH_ALGORITHM);
				if (source != null && source.exists()) {
					compactor.calcHash(this, hash, path, source);
				}
				String hashPath = encodeBytes(hash.digest());
				String targetExt = compactor.getTargetExtension(this, path);
				setProcessedPath(path, settings.getCDNRoot()+hashPath+targetExt);

				target = getTargetFile(path);
				if (source.exists()) {
					// ensure target path exists
					target.getParentFile().mkdirs();
		
					// ensure the file has been compacted
					compactor.compact(this, path, source, target);
				}
			}

			if (!target.exists()) {
				// file still missing, remove
				log.error(path+" failed to compact (output missing)");
				removeProcessedPath(path);
	
			} else if (target.length() < 1L) {
				if (source.length() < 1L) {
					// special case for files which compact to empty
					log.warn(path+" is an empty file");
	
					// remove from listings
					removeProcessedPath(path);
	
				} else {
					// special case for files which compact to empty
					log.warn(path+" compacted to an empty file (using original for merge)");
	
					// copy over original contents (as wasn't actually empty)
					new NullCompactor().compact(this, path, source, target);
				}
			}

		} finally {
			dependencyChain.pop();
		}
	}

	public void addChildLink(String path, String child) {
		List children = childLinkMap.get(path);
		if (children == null) {
			children = new ArrayList();
			childLinkMap.put(path, children);
		}
		if (!children.contains(child)) {
			children.add(child);
		}
	}

	public List getChildLinks(String path) {
		List children = childLinkMap.get(path);
		if (children == null) {
			children = Collections.emptyList();
		}
		return children;
	}

	public void addDependency(String path, String dependency) {
		List dependencies = dependencyMap.get(path);
		if (dependencies == null) {
			dependencies = new ArrayList();
			dependencyMap.put(path, dependencies);
		}
		if (!dependencies.contains(dependency)) {
			dependencies.add(dependency);
		}
	}

	public List getDependencies(String path) {
		List dependencies = dependencyMap.get(path);
		if (dependencies == null) {
			dependencies = Collections.emptyList();
		}
		return dependencies;
	}

	public static String getExtension(String path) {
		int dot = path.lastIndexOf('.');
		if (dot < 0) {
			return "";
		}

		return path.substring(dot).toLowerCase();
	}

	private static String encodeBytes(byte[] digest) {
		StringBuilder hex = new StringBuilder();
		for (int i=0; i findFiles()
			throws IOException {

		Set extensions = getExtensions();
		String filterPath = settings.getCDNDir().getCanonicalPath();
		Queue folders = new LinkedList();
		Map files = new LinkedHashMap();

		for (File inputDir : new File[] { settings.getSourceDir(), settings.getTargetDir() }) {
			int rootPrefix = inputDir.getCanonicalPath().length();

			folders.add(inputDir);
			while (!folders.isEmpty()) {
				File file = folders.remove();

				if (file.getCanonicalPath().startsWith(filterPath)) {
					// filter any output files, e.g., if dirs overlap
					continue;
				}

				if (file.isDirectory()) {
					folders.addAll(Arrays.asList(file.listFiles()));
					continue;
				}

				String ext = BuildManager.getExtension(file.getCanonicalPath());
				if (extensions.contains(ext)) {
					files.put(file, file.getCanonicalPath().substring(rootPrefix));
				}
			}
		}

		return files;
	}

	private Set getExtensions() {
		return this.compactors.keySet();
	}

	private void writeCompactionMap()
			throws IOException {

		File cdnMapFile = settings.getCDNMapFile();

		cdnMapFile.getParentFile().mkdirs();

		FileWriter writer = new FileWriter(cdnMapFile, false);
		try {
			writeCompactionMap(writer);

		} finally {
			writer.flush();
			writer.close();
		}
	}

	private void writeCompactionMap(Appendable output)
			throws IOException {

		// generate output
		for (String key : hashLookup.keySet()) {
			String value = hashLookup.get(key);
			value = escapePropertyValue(value);

			output
				.append(key)
				.append('=')
				.append(value)
				.append(NEWLINE);
		}
	}

	private void writeChildLinksMap()
			throws IOException {

		File cdnLinksFile = settings.getCDNLinksFile();
		cdnLinksFile.getParentFile().mkdirs();

		FileWriter writer = new FileWriter(cdnLinksFile, false);
		try {
			writeChildLinksMap(writer);

		} finally {
			writer.flush();
			writer.close();
		}
	}

	private void writeChildLinksMap(Appendable output)
			throws IOException {

		// propagate transitive children to dependents
		for (String path : this.dependencyMap.keySet()) {
			addTransitiveChildLinks(path);
		}

		// generate output
		for (String key : childLinkMap.keySet()) {
			List children = childLinkMap.get(key);

			boolean needsDelim = false;
			output
				.append(key)
				.append('=');

			for (String child : children) {
				if (needsDelim) {
					output.append(PROPERTY_LIST_DELIM);
				} else {
					needsDelim = true;
				}
				output.append(escapePropertyValue(child));
			}

			output.append(NEWLINE);
		}
	}

	private void addTransitiveChildLinks(String path) {
		if (!dependencyMap.containsKey(path)) {
			// no dependencies so nothing to propagate
			return;
		}

		List dependencies = dependencyMap.get(path);
		for (String dependency : dependencies) {
			// recursively ripple links up to dependent parents
			this.addTransitiveChildLinks(dependency);
			if (!childLinkMap.containsKey(dependency)) {
				// no child links so nothing to propagate
				continue;
			}

			// add as if was a direct child of dependent parent 
			List children = childLinkMap.get(dependency);
			for (String child : children) {
				// propagate child link up to aggregate parent
				this.addChildLink(path, child);
				log.info("transitive child link: "+path+"=>"+child);
			}
		}
	}

	/**
	 * http://download.oracle.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)
	 * @param value
	 * @return
	 */
	private static String escapePropertyValue(String value) {
		if (value == null) {
			return "";
		}

		StringBuilder output = null;
		int start = 0,
			length = value.length();

		for (int i=start; i start) {
						// emit any leading unescaped chunk
						output.append(value, start, i);
					}
					start = i+1;

					// emit escape
					output.append('\\').append(ch);
					continue;
			}
		}

		if (output == null) {
			// nothing to escape, can write entire string directly
			return value;
		}

		if (length > start) {
			// emit any trailing unescaped chunk
			output.append(value, start, length);
		}

		return output.toString();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy