/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.shrinkwrap.impl.base;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchiveEvent;
import org.jboss.shrinkwrap.api.ArchiveEventHandler;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.Configuration;
import org.jboss.shrinkwrap.api.Filter;
import org.jboss.shrinkwrap.api.IllegalArchivePathException;
import org.jboss.shrinkwrap.api.IllegalOverwriteException;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ArchiveAsset;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.exporter.StreamExporter;
import org.jboss.shrinkwrap.impl.base.path.BasicPath;
import org.jboss.shrinkwrap.impl.base.path.PathUtil;

MemoryMapArchiveBase A base implementation for all MemoryMap archives. Thread-safe.
Author:John Bailey, Aslak Knutsen
Type parameters:
  • <T> –
Version:$Revision: $
/** * MemoryMapArchiveBase * * A base implementation for all MemoryMap archives. Thread-safe. * * @author <a href="mailto:baileyje@gmail.com">John Bailey</a> * @author <a href="mailto:aslak@conduct.no">Aslak Knutsen</a> * @version $Revision: $ * @param <T> */
public abstract class MemoryMapArchiveBase<T extends Archive<T>> extends ArchiveBase<T> implements Archive<T> { // -------------------------------------------------------------------------------------|| // Instance Members -------------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
Storage for the Nodes.
/** * Storage for the {@link Node}s. */
private final Map<ArchivePath, NodeImpl> content = Collections.synchronizedMap(new LinkedHashMap<ArchivePath, NodeImpl>());
Storage for the ArchiveAssets. Used to help get access to nested archive content.
/** * Storage for the {@link ArchiveAsset}s. Used to help get access to nested archive content. */
private final Map<ArchivePath, ArchiveAsset> nestedArchives = Collections.synchronizedMap(new LinkedHashMap<ArchivePath, ArchiveAsset>()); private final List<ArchiveEventHandler> handlers = new ArrayList<ArchiveEventHandler>(); // -------------------------------------------------------------------------------------|| // Constructor ------------------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
Constructor This constructor will generate a unique Archive.getName() per instance.
Params:
  • configuration – The configuration for this archive
Throws:
/** * Constructor * * This constructor will generate a unique {@link Archive#getName()} per instance. * * @param configuration * The configuration for this archive * @throws IllegalArgumentException * If the configuration is not specified */
public MemoryMapArchiveBase(final Configuration configuration) throws IllegalArgumentException { this("Archive-" + UUID.randomUUID().toString() + ".jar", configuration); }
Constructor This constructor will generate an Archive with the provided name.
Params:
  • archiveName –
  • configuration – The configuration for this archive
Throws:
/** * Constructor * * This constructor will generate an {@link Archive} with the provided name. * * @param archiveName * @param configuration * The configuration for this archive * @throws IllegalArgumentException * If the name or configuration is not specified */
public MemoryMapArchiveBase(final String archiveName, final Configuration configuration) throws IllegalArgumentException { super(archiveName, configuration); // Add the root node to the content final ArchivePath rootPath = new BasicPath("/"); content.put(rootPath, new NodeImpl(rootPath)); } // -------------------------------------------------------------------------------------|| // Required Implementations - Archive -------------------------------------------------|| // -------------------------------------------------------------------------------------||
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#add(org.jboss.shrinkwrap.api.asset.Asset, * org.jboss.shrinkwrap.api.ArchivePath) */
@Override public T add(Asset asset, ArchivePath path) { Validate.notNull(asset, "No asset was specified"); Validate.notNull(path, "No path was specified"); return addAsset(path, asset); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#add(org.jboss.shrinkwrap.api.Archive, java.lang.String, java.lang.Class) */
@Override public T add(final Archive<?> archive, final String path, final Class<? extends StreamExporter> exporter) { Validate.notNull(archive, "Archive must be specified"); Validate.notNullOrEmpty(path, "Archive Path must be specified"); Validate.notNull(exporter, "exporter must be specified"); return this.add(archive, ArchivePaths.create(path), exporter); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.impl.base.ArchiveBase#add(org.jboss.shrinkwrap.api.Archive, * org.jboss.shrinkwrap.api.ArchivePath, java.lang.Class) */
@Override public T add(final Archive<?> archive, final ArchivePath path, final Class<? extends StreamExporter> exporter) { // Add archive asset super.add(archive, path, exporter); // Expected Archive Path final ArchivePath archivePath = new BasicPath(path, archive.getName()); // Get the Asset that was just added final Node node = get(archivePath); // Make sure it is an ArchiveAsset if (node.getAsset() != null && node.getAsset() instanceof ArchiveAsset) { final ArchiveAsset archiveAsset = ArchiveAsset.class.cast(node.getAsset()); // Add asset to ArchiveAsset Map nestedArchives.put(archivePath, archiveAsset); } return covariantReturn(); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#addAsDirectory(org.jboss.shrinkwrap.api.ArchivePath) */
@Override public T addAsDirectory(final ArchivePath path) throws IllegalArgumentException { // Precondition check Validate.notNull(path, "path must be specified"); // Adjust the path to remove any trailing slash ArchivePath adjustedPath = new BasicPath(PathUtil.optionallyRemoveFollowingSlash(path.get())); return addAsset(adjustedPath, null); } private T addAsset(ArchivePath path, Asset asset) { final Asset handledAsset = invokeHandlers(path, asset); // Disallow if we're dealing with a non-empty dir if (contains(path)) { if (asset != null) { // we're adding a file final Node node = this.get(path); if (node.getAsset() == null) { // Path exists as a dir, throw an exception throw new IllegalOverwriteException("Cannot add requested asset " + asset + " to path " + path.get() + " to archive " + this.getName() + "; path already exists as directory"); } else { // path exists as a file, overwrite addNewNode(path, handledAsset); } } // we're adding dir, it exists, do nothing } else { // Path does not exists, add new node addNewNode(path, handledAsset); } return covariantReturn(); } private void addNewNode(ArchivePath path, Asset handledAsset) { // Add the node to the content of the archive final NodeImpl newNode = new NodeImpl(path, handledAsset); content.put(path, newNode); // Add the new node to the parent as a child final NodeImpl parentNode = obtainParent(path.getParent()); if (parentNode != null) { parentNode.addChild(newNode); } }
{@inheritDoc}
See Also:
  • Archive.addListener(Filter, ArchiveEventHandler)
/** * {@inheritDoc} * @see org.jboss.shrinkwrap.api.Archive#addListener(org.jboss.shrinkwrap.api.Filter, org.jboss.shrinkwrap.api.ArchiveEventHandler) */
@Override public T addHandlers(ArchiveEventHandler... handlers) { for (ArchiveEventHandler handler : handlers) { this.handlers.add(handler); } return covariantReturn(); } private Asset invokeHandlers(ArchivePath path, Asset asset) { final ArchiveEvent event = new ArchiveEvent(path, asset); for (ArchiveEventHandler handler : handlers) { handler.handle(event); } return event.getHandledAsset(); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#contains(org.jboss.shrinkwrap.api.ArchivePath) */
@Override public boolean contains(ArchivePath path) { Validate.notNull(path, "No path was specified"); boolean found = content.containsKey(path); if (!found) { found = nestedContains(path); } return found; }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#contains(java.lang.String) */
@Override public boolean contains(final String path) throws IllegalArgumentException { Validate.notNull(path, "Path must be specified"); final ArchivePath archivePath = ArchivePaths.create(path); return this.contains(archivePath); }
{@inheritDoc}
See Also:
  • Archive.delete(ArchivePath)
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#delete(org.jboss.declarchive.api.ArchivePath) */
@Override public Node delete(ArchivePath path) { Validate.notNull(path, "No path was specified"); ArchivePath safePath = path; NodeImpl node = content.get(safePath); if (node == null) { if (path.get().endsWith("/")) { safePath = ArchivePaths.create(path.get().substring(0, path.get().length() - 1)); node = content.get(safePath); } if (node == null) { return null; } } return removeNodeRecursively(node, safePath); }
Removes the specified node and its associated children from the contents of this archive.
Params:
  • node – the node to remove recursively
  • path – the path denoting the specified node
Returns:the removed node itself
/** * Removes the specified node and its associated children from the contents * of this archive. * * @param node the node to remove recursively * @param path the path denoting the specified node * @return the removed node itself */
private Node removeNodeRecursively(final NodeImpl node, final ArchivePath path) { final NodeImpl parentNode = content.get(path.getParent()); if (parentNode != null) { parentNode.removeChild(node); } // Remove from nested archives if present nestedArchives.remove(path); // Recursively delete children if present if (node.getChildren() != null) { final Set<Node> children = node.getChildren(); // can't remove from collection inside of the iteration final Set<Node> childrenCopy = new HashSet<Node>(children); for (Node child : childrenCopy) { node.removeChild(child); content.remove(child.getPath()); } } return content.remove(path); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#delete(java.lang.String) */
@Override public Node delete(String archivePath) { Validate.notNull(archivePath, "No path was specified"); return delete(ArchivePaths.create(archivePath)); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#get(org.jboss.shrinkwrap.api.ArchivePath) */
@Override public Node get(ArchivePath path) { Validate.notNull(path, "No path was specified"); Node node = content.get(path); if (node == null && contains(path)) { node = getNestedNode(path); } return node; }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#getContent() */
@Override public Map<ArchivePath, Node> getContent() { Map<ArchivePath, Node> ret = new LinkedHashMap<ArchivePath, Node>(); for (Map.Entry<ArchivePath, NodeImpl> item : content.entrySet()) { if (!item.getKey().equals(new BasicPath("/"))) { ret.put(item.getKey(), item.getValue()); } } return Collections.unmodifiableMap(ret); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Archive#getContent(org.jboss.shrinkwrap.api.Filter) */
@Override public Map<ArchivePath, Node> getContent(Filter<ArchivePath> filter) { Validate.notNull(filter, "Filter must be specified"); Map<ArchivePath, Node> filteredContent = new LinkedHashMap<ArchivePath, Node>(); for (Map.Entry<ArchivePath, NodeImpl> contentEntry : content.entrySet()) { if (filter.include(contentEntry.getKey())) { if (!contentEntry.getKey().equals(new BasicPath("/"))) { filteredContent.put(contentEntry.getKey(), contentEntry.getValue()); } } } return filteredContent; } // -------------------------------------------------------------------------------------|| // Internal Helper Methods ------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
Check to see if a path is found in a nested archive
/** * Check to see if a path is found in a nested archive */
private boolean nestedContains(ArchivePath path) { // Iterate through nested archives for (Entry<ArchivePath, ArchiveAsset> nestedArchiveEntry : nestedArchives.entrySet()) { ArchivePath archivePath = nestedArchiveEntry.getKey(); ArchiveAsset archiveAsset = nestedArchiveEntry.getValue(); // Check to see if the requested path starts with the nested archive path if (startsWith(path, archivePath)) { Archive<?> nestedArchive = archiveAsset.getArchive(); // Get the asset path from within the nested archive ArchivePath nestedAssetPath = getNestedPath(path, archivePath); // Recurse the call to the nested archive return nestedArchive.contains(nestedAssetPath); } } return false; }
Attempt to get the asset from a nested archive.
Params:
  • path –
Returns:
/** * Attempt to get the asset from a nested archive. * * @param path * @return */
private Node getNestedNode(ArchivePath path) { // Iterate through nested archives for (Entry<ArchivePath, ArchiveAsset> nestedArchiveEntry : nestedArchives.entrySet()) { ArchivePath archivePath = nestedArchiveEntry.getKey(); ArchiveAsset archiveAsset = nestedArchiveEntry.getValue(); // Check to see if the requested path starts with the nested archive path if (startsWith(path, archivePath)) { Archive<?> nestedArchive = archiveAsset.getArchive(); // Get the asset path from within the nested archive ArchivePath nestedAssetPath = getNestedPath(path, archivePath); // Recurse the call to the nested archive return nestedArchive.get(nestedAssetPath); } } return null; }
Check to see if one path starts with another
Params:
  • fullPath –
  • startingPath –
Returns:
/** * Check to see if one path starts with another * * @param fullPath * @param startingPath * @return */
private boolean startsWith(ArchivePath fullPath, ArchivePath startingPath) { final String context = fullPath.get(); final String startingContext = startingPath.get(); return context.startsWith(startingContext); }
Given a full path and a base path return a new path containing the full path with the base path removed from the beginning.
Params:
  • fullPath –
  • basePath –
Returns:
/** * Given a full path and a base path return a new path containing the full path with the base path removed from the * beginning. * * @param fullPath * @param basePath * @return */
private ArchivePath getNestedPath(ArchivePath fullPath, ArchivePath basePath) { final String context = fullPath.get(); final String baseContent = basePath.get(); // Remove the base path from the full path String nestedArchiveContext = context.substring(baseContent.length()); return new BasicPath(nestedArchiveContext); }
Used to retrieve a Node from the content of the Archive. If the Node doesn�t exists in the specified location, it is created and added to the Archive. The same happens to all its non-existing parents. However, if the Node is an asset, an IllegalArchivePathException is thrown.
Params:
Throws:
Returns:The Node in the specified path
/** * Used to retrieve a {@link Node} from the content of the {@link Archive}. If the {@link Node} doesn�t exists in * the specified location, it is created and added to the {@link Archive}. The same happens to all its non-existing * parents. However, if the {@link Node} is an asset, an IllegalArchivePathException is thrown. * * @param path * The {@link ArchivePath} from which we are obtaining the {@link Node} * @return The {@link Node} in the specified path * @throws IllegalArchivePathException * if the node is an {@link Asset} */
private NodeImpl obtainParent(ArchivePath path) { if (path == null) { return null; } NodeImpl node = content.get(path); // If the node exists, just return it if (node != null) { // if the node is an asset, throw an exception if (node.getAsset() != null) { throw new IllegalArchivePathException("Could not create node under " + path.getParent() + ". It points to an asset."); } return node; } // If the node doesn't exists, create it. Also create all possible non-existing // parents node = new NodeImpl(path); NodeImpl parentNode = obtainParent(path.getParent()); if (parentNode != null) { parentNode.addChild(node); } // Add the node to the contents of the archive content.put(path, node); return node; } }