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

jcommon.extract.Resources Maven / Gradle / Ivy

Go to download

Java library for simple extracting and processing of embedded resources at runtime.

The newest version!

package jcommon.extract;

import jcommon.core.Namespaces;
import jcommon.core.StringUtil;
import jcommon.init.Loader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

import static jcommon.extract.ResourceUtils.createUnprivilegedExecutorService;
import static jcommon.extract.ResourceUtils.stringAttributeValue;

/**
 * Reads a simple XML file that describes files to be extracted.
 *
 * @author David Hoyt 
 */
public class Resources {
	//
	public static final String
		  ATTRIBUTE_NAME		= "name"
	;

	public static final Resources
		  Empty = new Resources(StringUtil.empty, new IResourcePackage[0])
	;

	public static final Future CompletedFuture = new Future() {
		@Override
		public final boolean cancel(boolean bln) {
			return true;
		}

		@Override
		public final boolean isCancelled() {
			return false;
		}

		@Override
		public final boolean isDone() {
			return true;
		}

		@Override
		public final Object get() throws InterruptedException, ExecutionException {
			return null;
		}

		@Override
		public final Object get(long l, TimeUnit tu) throws InterruptedException, ExecutionException, TimeoutException {
			return null;
		}
	};
	//

	//
	protected Reference registryReference = null;
	protected String name;
	protected boolean loaded = false;
	protected boolean processed = false;
	protected final Object lock = new Object();
	protected long totalResourceSize;
	protected int totalResourceCount;
	protected IResourcePackage[] packages;
	protected String[] references;
	//

	//
	public Resources(final IResourcePackage... Packages) {
		initFromPackages(StringUtil.empty, null, Packages);
	}

	public Resources(final String Name, final IResourcePackage... Packages) {
		initFromPackages(Name, null, Packages);
	}

	public Resources(final String[] References, final IResourcePackage... Packages) {
		initFromPackages(StringUtil.empty, References, Packages);
	}

	public Resources(final String Name, final String[] References, final IResourcePackage... Packages) {
		initFromPackages(Name, References, Packages);
	}
	
	public Resources(final IVariableProcessor VariableProcessor, final InputStream XMLData) throws XPathException, ParserConfigurationException, SAXException, IOException {
		initFromXML(ResourceProcessorFactory.DEFAULT_INSTANCE, VariableProcessor, XMLData);
	}

	public Resources(final ResourceProcessorFactory ProcessorFactory, final IVariableProcessor VariableProcessor, final InputStream XMLData) throws XPathException, ParserConfigurationException, SAXException, IOException {
		initFromXML(ProcessorFactory, VariableProcessor, XMLData);
	}

	protected void initFromPackages(final String Name, final String[] References, final IResourcePackage[] Packages) {
		if (Packages == null)
			throw new java.lang.NullPointerException("Processors cannot be null");

		this.name = Name;
		this.packages = Packages;
		this.references = References;
		
		initAfter();
	}

	protected void initFromXML(final ResourceProcessorFactory ProcessorFactory, final IVariableProcessor VariableProcessor, final InputStream XMLData) throws XPathException, ParserConfigurationException, SAXException, IOException {
		final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		final DocumentBuilder builder = factory.newDocumentBuilder();
		final XPathFactory xpathFactory = XPathFactory.newInstance();
		final XPath xpath = xpathFactory.newXPath();

		//Creates a context that always has "jcommon" as a prefix -- useful for xpath evaluations
		xpath.setNamespaceContext(Namespaces.createNamespaceContext());

		this.packages = read(ProcessorFactory, VariableProcessor, xpath, builder.parse(XMLData));

		initAfter();
	}

	protected void initAfter() {
		calculateTotals();
		loaded = true;
	}
	//

	//
	protected static void checkRegistryInitialization() {
		if (!Loader.areRegistryReferencesInitialized()) {
			try {
				Loader.initializeRegistryReferences();
			} catch(Throwable t) {
				if (t instanceof ResourceException)
					throw (ResourceException)t;
				else
					throw new ResourceException("Error initializing registry", t);
			}
		}
	}

	protected void calculateTotals() {
		//Calculate total size and resource count
		for(IResourcePackage pkg : packages) {
			if (pkg == null)
				continue;

			totalResourceCount += pkg.getTotalResourceCount();
			totalResourceSize += pkg.getTotalSize();
		}
	}

	protected void notifyProgressBegin(jcommon.extract.IResourceProgressListener progress, long startTime) {
		if (progress != null)
			progress.begin(getTotalResourceCount(), getTotalPackageCount(), getTotalResourceSize(), startTime);
	}

	protected void notifyProgressReportResourceCompleted(IResourceProgressListener progress, IResourceProcessor resource, IResourcePackage pkg, long totalBytes, int totalResources, int totalPkgs, long startTime, String message) {
		if (progress != null)
			progress.reportResourceComplete(resource, pkg, getTotalResourceCount(), getTotalPackageCount(), getTotalResourceSize(), totalBytes, totalResources, totalPkgs, startTime, Math.abs(System.currentTimeMillis() - startTime), message);
	}

	protected void notifyProgressReportPackageCompleted(IResourceProgressListener progress, IResourcePackage pkg, long totalBytes, int totalResources, int totalPkgs, long startTime, String message) {
		if (progress != null)
			progress.reportPackageComplete(pkg, getTotalResourceCount(), getTotalPackageCount(), getTotalResourceSize(), totalBytes, totalResources, totalPkgs, startTime, Math.abs(System.currentTimeMillis() - startTime), message);
	}

	protected void notifyProgressError(IResourceProgressListener progress, Throwable exception, String message) {
		if (progress != null)
			progress.error(exception, message);
	}

	protected void notifyProgressEnd(IResourceProgressListener progress, boolean success, long totalBytes, int totalResources, int totalPkgs, long startTime, long endTime) {
		if (progress != null)
			progress.end(success, getTotalResourceCount(), getTotalPackageCount(), getTotalResourceSize(), totalBytes, totalResources, totalPkgs, startTime, endTime);
	}
	//

	//
	public String getName() {
		return name;
	}
	
	public boolean isLoaded() {
		return loaded;
	}

	public boolean isProcessed() {
		return processed;
	}

	public String[] getReferences() {
		return references;
	}
	
	public int getTotalPackageCount() {
		return packages.length;
	}
	
	public int getTotalResourceCount() {
		return totalResourceCount;
	}

	public long getTotalResourceSize() {
		return totalResourceSize;
	}
	
	public IResourcePackage[] getPackages() {
		return packages;
	}

	public Reference getRegistryReference() {
		return registryReference;
	}
	//

	//
	//
	public Future extract() {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, IResourceFilter.None, IResourceProgressListener.None, IResourceCallback.None);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final IResourceCallback callback) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, IResourceFilter.None, IResourceProgressListener.None, callback);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final IResourceProgressListener progress) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, IResourceFilter.None, progress, IResourceCallback.None);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final IResourceFilter filter) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, filter, IResourceProgressListener.None, IResourceCallback.None);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final IResourceFilter filter, final IResourceCallback callback) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, filter, IResourceProgressListener.None, callback);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final IResourceProgressListener progress, final IResourceCallback callback) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, IResourceFilter.None, progress, callback);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final IResourceFilter filter, final IResourceProgressListener progress, final IResourceCallback callback) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return extract(references, packages, svc, filter, progress, callback);
		} finally {
			svc.shutdown();
		}
	}

	public Future extract(final ExecutorService executor, final IResourceFilter filter, final IResourceProgressListener progress, final IResourceCallback callback) {
		return extract(references, packages, executor, filter, progress, callback);
	}
	//

	/* public boolean preprocessEnvVars() {
		return processEnvVars(packages, IResourceFilter.None);
	}
	
	public boolean preprocessEnvVars(final IResourceFilter filter) {
		return processEnvVars(packages, filter);
	} /**/

	public Future extract(final String[] refs, final IResourcePackage[] pkgs, final ExecutorService executor, final IResourceFilter filter, final IResourceProgressListener progress, final IResourceCallback callback) {
		checkRegistryInitialization();

		synchronized(lock) {
			if (processed) {
				//If we've already processed this resource, then just mimic a
				//typical extraction process but don't actually do anything.
				if (progress == null)
					return CompletedFuture;

				//Extract nothing, but make sure that
				return executor.submit(new Runnable() {
					@Override
					public void run() {
						try {
							//
							if (false)
								throw new InterruptedException();
							//

							//
							if (callback != null)
								callback.prepare(Resources.this);
							//

							if (progress != null) {
								long startTime = System.currentTimeMillis();
								notifyProgressBegin(progress, startTime);
								notifyProgressEnd(progress, true, totalResourceSize, totalResourceCount, packages != null ? packages.length : 0, startTime, startTime);
							}

							//
							if (callback != null)
								callback.completed(Resources.this);
							//
						} catch(InterruptedException ie) {
							//
							if (callback != null)
								callback.cancelled(Resources.this);
							//
						} catch(CancellationException ce) {
							//
							if (callback != null)
								callback.cancelled(Resources.this);
							//
						} catch(RejectedExecutionException ree) {
							//
							if (callback != null)
								callback.cancelled(Resources.this);
							//
						} catch(Throwable t) {
							//
							if (progress != null)
								progress.error(t, t.getMessage());
							if (callback != null)
								callback.error(Resources.this);
							//
						}
					}
				});
			}

			return executor.submit(new Runnable() {
				@Override
				public void run() {
					synchronized(lock) {
						try {
							//
							if (false)
								throw new InterruptedException();
							//

							//
							if (callback != null)
								callback.prepare(Resources.this);
							//

							if (!processed) {
								//Do the real work
								extractResources(refs, pkgs, filter, progress);

								//Mark as processed
								processed = true;
							}

							//
							if (callback != null)
								callback.completed(Resources.this);
							//
						} catch(InterruptedException ie) {
							//
							if (callback != null)
								callback.cancelled(Resources.this);
							//
						} catch(CancellationException ce) {
							//
							if (callback != null)
								callback.cancelled(Resources.this);
							//
						} catch(RejectedExecutionException ree) {
							//
							if (callback != null)
								callback.cancelled(Resources.this);
							//
						} catch(Throwable t) {
							//
							if (callback != null)
								callback.error(Resources.this);
							//
						}
					}
				}
			});
		}
	}
	//

	//
	//
	public static final Future extractAll(final IResourcePackage... packages) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return newInstance((String[])null, packages).extract(svc, IResourceFilter.None, IResourceProgressListener.None, IResourceCallback.None);
		} finally {
			svc.shutdown();
		}
	}

	public static final Future extractAll(final String[] refs, final IResourcePackage... packages) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return newInstance(refs, packages).extract(svc, IResourceFilter.None, IResourceProgressListener.None, IResourceCallback.None);
		} finally {
			svc.shutdown();
		}
	}

	public static final Future extractAll(final IResourceCallback callback, final IResourcePackage... packages) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return newInstance((String[])null, packages).extract(svc, IResourceFilter.None, IResourceProgressListener.None, callback);
		} finally {
			svc.shutdown();
		}
	}

	public static final Future extractAll(final IResourceProgressListener progress, final IResourceCallback callback, final IResourcePackage... packages) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return newInstance((String[])null, packages).extract(svc, IResourceFilter.None, progress, callback);
		} finally {
			svc.shutdown();
		}
	}

	public static final Future extractAll(final IResourceFilter filter, final IResourceProgressListener progress, final IResourceCallback callback, final IResourcePackage... packages) {
		final ExecutorService svc = createUnprivilegedExecutorService();
		try {
			return newInstance((String[])null, packages).extract(svc, filter, progress, callback);
		} finally {
			svc.shutdown();
		}
	}

	public static final Future extractAll(final ExecutorService executor, final IResourceFilter filter, final IResourceProgressListener progress, final IResourceCallback callback, final IResourcePackage... packages) {
		return newInstance((String[])null, packages).extract(executor, filter, progress, callback);
	}

	public static final Future extractAll(final ExecutorService executor, final IResourceFilter filter, final IResourceProgressListener progress, final IResourceCallback callback, final String[] refs, final IResourcePackage... packages) {
		return newInstance(refs, packages).extract(executor, filter, progress, callback);
	}
	//

	//
	public static final Resources newInstance(final IResourcePackage... Packages) {
		return new Resources(Packages);
	}

	public static final Resources newInstance(final String Name, final IResourcePackage... Packages) {
		return new Resources(Name, Packages);
	}
	
	public static final Resources newInstance(final String[] References, final IResourcePackage... Packages) {
		return new Resources(References, Packages);
	}

	public static final Resources newInstance(final String Name, final String[] References, final IResourcePackage... Packages) {
		return new Resources(Name, References, Packages);
	}

	public static final Resources newInstance(final String ResourceName) {
		return newInstance(VariableProcessorFactory.newInstance(), ResourceName);
	}

	public static final Resources newInstance(final File XMLFile) {
		return newInstance(VariableProcessorFactory.newInstance(), XMLFile);
	}

	public static final Resources newInstance(final InputStream XMLData) {
		return newInstance(VariableProcessorFactory.newInstance(), XMLData);
	}

	public static final Resources newInstance(final ResourceProcessorFactory ProcessorFactory, final String ResourceName) {
		return newInstance(ProcessorFactory, VariableProcessorFactory.newInstance(), ResourceName);
	}

	public static final Resources newInstance(final ResourceProcessorFactory ProcessorFactory, final File XMLFile) {
		return newInstance(ProcessorFactory, VariableProcessorFactory.newInstance(), XMLFile);
	}

	public static final Resources newInstance(final ResourceProcessorFactory ProcessorFactory, final InputStream XMLData) {
		return newInstance(ProcessorFactory, VariableProcessorFactory.newInstance(), XMLData);
	}

	public static final Resources newInstance(final IVariableProcessor VariableProcessor, final String ResourceName) {
		return newInstance(ResourceProcessorFactory.DEFAULT_INSTANCE, VariableProcessor, ResourceName);
	}

	public static final Resources newInstance(final IVariableProcessor VariableProcessor, final File XMLFile) {
		return newInstance(ResourceProcessorFactory.DEFAULT_INSTANCE, VariableProcessor, XMLFile);
	}

	public static final Resources newInstance(final IVariableProcessor VariableProcessor, final InputStream XMLData) {
		return newInstance(ResourceProcessorFactory.DEFAULT_INSTANCE, VariableProcessor, XMLData);
	}

	public static final Resources newInstance(final ResourceProcessorFactory ProcessorFactory, final IVariableProcessor VariableProcessor, final String ResourceName) {
		return newInstance(ProcessorFactory, VariableProcessor, Resources.class.getResourceAsStream(ResourceName));
	}

	public static final Resources newInstance(final ResourceProcessorFactory ProcessorFactory, final IVariableProcessor VariableProcessor, final File XMLFile) {
		FileInputStream fis = null;
		try {
			return newInstance(ProcessorFactory, VariableProcessor, (fis = new FileInputStream(XMLFile)));
		} catch(ResourceException t) {
			throw t;
		} catch(Throwable t) {
			throw new ResourceException("Unable to preprocess resource file", t);
		} finally {
			try {
				if (fis != null)
					fis.close();
			} catch(IOException ie) {
			}
		}
	}

	public static final Resources newInstance(final ResourceProcessorFactory ProcessorFactory, final IVariableProcessor VariableProcessor, final InputStream XMLData) {
		try {
			return new Resources(VariableProcessor, XMLData);
		} catch(ResourceException t) {
			throw t;
		} catch(Throwable t) {
			throw new ResourceException("Unable to preprocess resource file", t);
		}
	}
	//
	//

	//
	protected IResourcePackage[] read(final ResourceProcessorFactory processorFactory, final IVariableProcessor variableProcessor, final XPath xpath, final Document document) throws XPathException  {
		Node node;
		NodeList lst;
		IResourcePackage pkg;
		List pkgs = new ArrayList(1);

		//Collapse whitespace nodes
		document.normalize();

		//Get the top-level document element, 
		final Element top = document.getDocumentElement();

		//
		if ((node = (Node)xpath.evaluate("//Resources", top, XPathConstants.NODE)) != null) {
			this.name = stringAttributeValue(variableProcessor, StringUtil.empty, node, ATTRIBUTE_NAME);

			if (StringUtil.isNullOrEmpty(name))
				throw new ResourceException("Invalid resource name. It cannot be empty.");

			this.registryReference = Registry.add(name, this);
		}
		//

		//
		//Locate  tags
		if ((lst = (NodeList)xpath.evaluate("//Resources/References/Add", top, XPathConstants.NODESET)) != null && lst.getLength() > 0) {
			final List refs = new ArrayList(lst.getLength());

			//Iterate over every  tag
			for(int i = 0; i < lst.getLength() && (node = lst.item(i)) != null; ++i) {
				final String refName = stringAttributeValue(variableProcessor, StringUtil.empty, node, ATTRIBUTE_NAME);
				if (!StringUtil.isNullOrEmpty(refName) && !this.name.equalsIgnoreCase(refName))
					refs.add(refName);
			}

			this.references = refs.toArray(new String[refs.size()]);
		}
		//

		//
		//Locate  tags
		if ((lst = (NodeList)xpath.evaluate("//Resources/Extract", top, XPathConstants.NODESET)) == null || lst.getLength() <= 0)
			return IResourcePackage.EMPTY;

		//Iterate over every  tag
		for(int i = 0; i < lst.getLength() && (node = lst.item(i)) != null; ++i) {

			//Ask the package to read it
			if ((pkg = Package.newInstance(processorFactory, variableProcessor, node, xpath, document)) != null)
				pkgs.add(pkg);
		}
		//

		//Create an array and return it
		return pkgs.toArray(new IResourcePackage[pkgs.size()]);
	}

	protected void extractResources(final String[] refs, final IResourcePackage[] pkgs, final IResourceFilter filter, final IResourceProgressListener progress) throws Throwable {
		extractResources(0, refs, pkgs, filter, progress);
	}

	protected void extractResources(final int level, final String[] refs, final IResourcePackage[] pkgs, final IResourceFilter filter, final IResourceProgressListener progress) throws Throwable {
		//Please note that this is executed in a separate thread.
		//It can be cancelled or interrupted at any time.

		int totalPkgs = 0;
		int totalResources = 0;
		long totalBytes = 0;
		String resourceName;
		long endTime;
		boolean success = true;
		Throwable exception = null;
		String title = null;
		long startTime = System.currentTimeMillis();

		try {
			//Notify begin
			if (level == 0)
				notifyProgressBegin(progress, startTime);

			//Load references
			if (refs != null && refs.length > 0) {
				for(String ref : refs) {
					if (ref.equalsIgnoreCase(name))
						continue;
					if (StringUtil.isNullOrEmpty(ref))
						throw new MissingResourceReferenceException("An empty reference name is invalid.");

					//Attempt to locate this reference
					final Reference reference = Registry.findReference(ref, true);
					if (reference == null)
						continue;

					//Get its associated resource object if it has one
					final Resources res = reference.getResources();
					if (res == null || res == this || res.isProcessed())
						continue;

					//Process on this same thread
					res.extractResources(level + 1, res.references, res.packages, filter, progress);
				}
			}

			for(IResourcePackage pkg : pkgs) {
				if (pkg == null) {
					notifyProgressReportPackageCompleted(progress, pkg, totalBytes, totalResources, ++totalPkgs, startTime, "Skipped package");
					continue;
				}

				for(IResourceProcessor p : pkg) {
					if (p == null) {
						notifyProgressReportResourceCompleted(progress, p, pkg, totalBytes, ++totalResources, totalPkgs, startTime, "Skipped resource");
						continue;
					}

					//Double check that we're allowed to process this resource
					resourceName = pkg.resourcePath(p.getName());
					if (filter != null && !filter.filter(pkg, p, resourceName)) {
						notifyProgressReportResourceCompleted(progress, p, pkg, totalBytes, ++totalResources, totalPkgs, startTime, "Skipped resource");
						continue;
					}

					//Here we go!
					//If this was already processed, then skip the processing again
					if (p.isProcessed() || p.process(resourceName, pkg, filter, progress)) {
						totalBytes += p.getSize();
						title = (p instanceof DefaultResourceProcessor ? ((DefaultResourceProcessor)p).getTitle() : p.getName());
						if (StringUtil.isNullOrEmpty(title))
							title = p.getName();
						notifyProgressReportResourceCompleted(progress, p, pkg, totalBytes, ++totalResources, totalPkgs, startTime, title);
					}
				}

				notifyProgressReportPackageCompleted(progress, pkg, totalBytes, totalResources, ++totalPkgs, startTime, "Completed package");
			}
			success = true;
		} catch(Throwable t) {
			success = false;
			exception = t;
		} finally {
			endTime = System.currentTimeMillis();
		}

		if (exception != null)
			notifyProgressError(progress, exception, !StringUtil.isNullOrEmpty(exception.getMessage()) ? exception.getMessage() : "Error loading resources");

		if (level == 0)
			notifyProgressEnd(progress, success, totalBytes, totalResources, totalPkgs, startTime, endTime);
	}
	//
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy