#region License
/*
* Copyright 2002-2005 the original author or authors.
*
* 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.
*/
#endregion
#region Imports
using System;
using System.IO;
using System.Text;
using Spring.Util;
#endregion
namespace Spring.Core.IO{
/// <summary>
/// Convenience base class for <see cref="Spring.Core.IO.IResource"/>
/// implementations, pre-implementing typical behavior.
/// </summary>
/// <remarks>
/// <p>
/// The <see cref="Spring.Core.IO.AbstractResource.Exists"/> method will
/// check whether a <see cref="System.IO.FileInfo"/> or
/// <see cref="System.IO.Stream"/> can be opened;
/// <see cref="Spring.Core.IO.AbstractResource.IsOpen"/> will always return
/// <see langword="false"/>;
/// <see cref="Spring.Core.IO.AbstractResource.Uri"/> and
/// <see cref="Spring.Core.IO.AbstractResource.File"/> throw an exception;
/// and <see cref="Spring.Core.IO.AbstractResource.ToString()"/> will
/// return the value of the
/// <see cref="Spring.Core.IO.IResource.Description"/> property.
/// </p>
/// </remarks>
/// <author>Juergen Hoeller</author>
/// <author>Rick Evans (.NET)</author>
/// <author>Aleksandar Seovic (.NET)</author>
/// <seealso cref="Spring.Core.IO.IResource"/>
public abstract class AbstractResource : IResource
{
/// <summary>
/// The default special character that denotes the base (home, or root)
/// path.
/// </summary>
/// <remarks>
/// <p>
/// Will be resolved (by those <see cref="Spring.Core.IO.IResource"/>
/// implementations that support it) to the home (or root) path for
/// the specific <see cref="Spring.Core.IO.IResource"/> implementation.
/// </p>
/// <p>
/// For example, in the case of a web application this will (probably)
/// resolve to the virtual directory of said web application.
/// </p>
/// </remarks>
protected const string DefaultBasePathPlaceHolder = "~";
private string protocol;
private string resourceName;
private string basePathPlaceHolder = DefaultBasePathPlaceHolder;
#region Constructor (s) / Destructor
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Core.IO.AbstractResource"/> class.
/// </summary>
/// <remarks>
/// <p>
/// This is an <see langword="abstract"/> class, and as such exposes no
/// public constructors.
/// </p>
/// </remarks>
protected AbstractResource()
{
}
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Core.IO.AbstractResource"/> class.
/// </summary>
/// <remarks>
/// <p>
/// This is an <see langword="abstract"/> class, and as such exposes no
/// public constructors.
/// </p>
/// </remarks>
/// <param name="resourceName">
/// A string representation of the resource.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// If the supplied <paramref name="resourceName"/> is
/// <see langword="null"/> or contains only whitespace character(s).
/// </exception>
protected AbstractResource(string resourceName)
{
AssertUtils.ArgumentHasText(resourceName, "resourceName");
this.protocol = ConfigurableResourceLoader.GetProtocol(resourceName);
this.resourceName = resourceName;
}
#endregion
#region Properties
/// <summary>
/// The special character that denotes the base (home, or root)
/// path.
/// </summary>
/// <remarks>
/// <p>
/// Will be resolved (by those <see cref="Spring.Core.IO.IResource"/>
/// implementations that support it) to the home (or root) path for
/// the specific <see cref="Spring.Core.IO.IResource"/> implementation.
/// </p>
/// <p>
/// For example, in the case of a web application this will (probably)
/// resolve to the virtual directory of said web application.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Core.IO.AbstractResource.DefaultBasePathPlaceHolder"/>
public string BasePathPlaceHolder
{
get { return basePathPlaceHolder; }
set { basePathPlaceHolder = value; }
}
/// <summary>
/// Return an <see cref="System.IO.Stream"/> for this resource.
/// </summary>
/// <value>
/// An <see cref="System.IO.Stream"/>.
/// </value>
/// <exception cref="System.IO.IOException">
/// If the stream could not be opened.
/// </exception>
/// <seealso cref="Spring.Core.IO.IInputStreamSource.InputStream"/>
public abstract Stream InputStream { get; }
/// <summary>
/// Returns a description for this resource.
/// </summary>
/// <value>
/// A description for this resource.
/// </value>
/// <seealso cref="Spring.Core.IO.IResource.Description"/>
public abstract string Description { get; }
/// <summary>
/// Returns the protocol associated with this resource (if any).
/// </summary>
/// <remarks>
/// <p>
/// The value of this property may be <see langword="null"/> if no
/// protocol is associated with the resource type (for example if the
/// resource is a memory stream).
/// </p>
/// </remarks>
/// <value>
/// The protocol associated with this resource (if any).
/// </value>
public string Protocol
{
get { return protocol; }
}
/// <summary>
/// Does this resource represent a handle with an open stream?
/// </summary>
/// <remarks>
/// <p>
/// This, the default implementation, always returns
/// <see langword="false"/>.
/// </p>
/// </remarks>
/// <value>
/// <see langword="true"/> if this resource represents a handle with an
/// open stream.
/// </value>
/// <seealso cref="Spring.Core.IO.IResource.IsOpen"/>
public virtual bool IsOpen
{
get { return false; }
}
#if !NET_2_0
/// <summary>
/// Returns the <see cref="System.Uri"/> handle for this resource.
/// </summary>
/// <remarks>
/// <p>
/// This, the default implementation, always throws a
/// <see cref="System.IO.FileNotFoundException"/>, assuming that the
/// resource cannot be exposed as a <see cref="System.Uri"/>.
/// </p>
/// </remarks>
/// <value>
/// The <see cref="System.Uri"/> handle for this resource.
/// </value>
/// <exception cref="System.IO.FileNotFoundException">
/// This, the default implementation, <b>always</b> throws a
/// <see cref="System.IO.FileNotFoundException"/>.
/// </exception>
/// <seealso cref="Spring.Core.IO.IResource.Uri"/>
public virtual Uri Uri
{
get
{
throw new FileNotFoundException(
Description + " cannot be resolved to a Uri.");
}
}
#else
/// <summary>
/// Returns the <see cref="System.Uri"/> handle for this resource.
/// </summary>
/// <seealso cref="Spring.Core.IO.IResource.Uri"/>
public virtual Uri Uri
{
get
{
return new Uri(resourceName);
}
}
#endif
/// <summary>
/// Returns a <see cref="System.IO.FileInfo"/> handle for this resource.
/// </summary>
/// <remarks>
/// <p>
/// This, the default implementation, always throws a
/// <see cref="System.IO.FileNotFoundException"/>, assuming that the
/// resource cannot be resolved to an absolute file path.
/// </p>
/// </remarks>
/// <value>
/// The <see cref="System.IO.FileInfo"/> handle for this resource.
/// </value>
/// <exception cref="System.IO.FileNotFoundException">
/// This implementation <b>always</b> throws a
/// <see cref="System.IO.FileNotFoundException"/>.
/// </exception>
/// <seealso cref="Spring.Core.IO.IResource"/>
/// <see cref="Spring.Core.IO.IResource.Exists"/>
public virtual FileInfo File
{
get
{
throw new FileNotFoundException(
Description + " cannot be resolved to an absolute file path.");
}
}
/// <summary>
/// Does this resource actually exist in physical form?
/// </summary>
/// <remarks>
/// <p>
/// This implementation checks whether a <see cref="System.IO.FileInfo"/>
/// can be opened, falling back to whether a
/// <see cref="System.IO.Stream"/> can be opened.
/// </p>
/// <p>
/// This will cover both directories and content resources.
/// </p>
/// <p>
/// This implementation will also return <see langword="false"/> if
/// permission to the (file's) path is denied.
/// </p>
/// </remarks>
/// <value>
/// <see langword="true"/> if this resource actually exists in physical
/// form (for example on a filesystem).
/// </value>
/// <seealso cref="Spring.Core.IO.IResource.Exists"/>
/// <seealso cref="Spring.Core.IO.IResource.File"/>
public virtual bool Exists
{
get
{
try
{
return File.Exists;
}
catch (IOException)
{
try
{
Stream inputStream = InputStream;
inputStream.Close();
return true;
} catch (Exception)
{
return false;
}
}
}
}
#endregion
#region Methods
/// <summary>
/// Strips any protocol name from the supplied
/// <paramref name="resourceName"/>.
/// </summary>
/// <remarks>
/// <p>
/// If the supplied <paramref name="resourceName"/> does not
/// have any protocol associated with it, then the supplied
/// <paramref name="resourceName"/> will be returned as-is.
/// </p>
/// </remarks>
/// <example>
/// <code language="C#">
/// GetResourceNameWithoutProtocol("http://www.mycompany.com/resource.txt");
/// // returns www.mycompany.com/resource.txt
/// </code>
/// </example>
/// <param name="resourceName">
/// The name of the resource.
/// </param>
/// <returns>
/// The name of the resource without the protocol name.
/// </returns>
protected static string GetResourceNameWithoutProtocol(string resourceName)
{
int pos = resourceName.IndexOf(
ConfigurableResourceLoader.ProtocolSeparator);
if (pos == -1)
{
return resourceName;
}
else
{
return resourceName.Substring(pos + ConfigurableResourceLoader.ProtocolSeparator.Length);
}
}
/// <summary>
/// Resolves the supplied <paramref name="resourceName"/> to its value
/// sans any leading protocol.
/// </summary>
/// <param name="resourceName">
/// The name of the resource.
/// </param>
/// <returns>
/// The name of the resource without the protocol name.
/// </returns>
/// <see cref="Spring.Core.IO.AbstractResource.GetResourceNameWithoutProtocol"/>
protected virtual string ResolveResourceNameWithoutProtocol(string resourceName)
{
return ResolveBasePathPlaceHolder(
GetResourceNameWithoutProtocol(resourceName), BasePathPlaceHolder);
}
/// <summary>
/// Resolves the presence of the
/// <paramref name="basePathPlaceHolder"/> value
/// in the supplied <paramref name="resourceName"/> into a path.
/// </summary>
/// <remarks>
/// <p>
/// The default implementation simply returns the supplied
/// <paramref name="resourceName"/> as is.
/// </p>
/// </remarks>
/// <param name="resourceName">
/// The name of the resource.
/// </param>
/// <param name="basePathPlaceHolder">
/// The string that is a placeholder for a base path.
/// </param>
/// <returns>
/// The name of the resource with any <paramref name="basePathPlaceHolder"/>
/// value having been resolved into an actual path.
/// </returns>
protected virtual string ResolveBasePathPlaceHolder(
string resourceName, string basePathPlaceHolder)
{
return resourceName;
}
/// <summary>
/// This implementation returns the
/// <see cref="Description"/> of this resource.
/// </summary>
/// <seealso cref="Spring.Core.IO.IResource.Description"/>
public override string ToString()
{
return Description;
}
/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is
/// equal to the current <see cref="System.Object"/>.
/// </summary>
/// <remarks>
/// <p>
/// This implementation compares <see cref="Description"/> values.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Core.IO.IResource.Description"/>
public override bool Equals(object obj)
{
return obj is IResource
&& ((IResource)obj).Description.Equals(Description);
}
/// <summary>
/// Serves as a hash function for a particular type, suitable for use
/// in hashing algorithms and data structures like a hash table.
/// </summary>
/// <remarks>
/// <p>
/// This implementation returns the hashcode of the
/// <see cref="Description"/> property.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Core.IO.IResource.Description"/>
public override int GetHashCode()
{
return Description.GetHashCode();
}
#endregion
#region Relative Resource Support
/// <summary>
/// Factory Method. Create a new instance of the current resource type using the given resourceName
/// </summary>
protected virtual IResource CreateResourceInstance( string resourceName )
{
return null;
}
/// <summary>
/// The ResourceLoader to be used for resolving relative resources
/// </summary>
protected virtual IResourceLoader GetResourceLoader()
{
return new ConfigurableResourceLoader();
}
/// <summary>
/// Does this <see cref="Spring.Core.IO.IResource"/> support relative
/// resource retrieval?
/// </summary>
/// <remarks>
/// <p>
/// This property is generally to be consulted prior to attempting
/// to attempting to access a resource that is relative to this
/// resource (via a call to
/// <see cref="Spring.Core.IO.IResource.CreateRelative"/>).
/// </p>
/// <p>
/// This, the default implementation, always returns
/// <see langword="false"/>.
/// </p>
/// </remarks>
/// <value>
/// <see langword="true"/> if this
/// <see cref="Spring.Core.IO.IResource"/> supports relative resource
/// retrieval.
/// </value>
protected virtual bool SupportsRelativeResources
{
get { return false; }
}
/// <summary>
/// Gets the root location of the resource.
/// </summary>
/// <remarks>
/// <p>
/// Where root resource can be taken to mean that part of the resource
/// descriptor that doesn't change when a relative resource is looked
/// up. Examples of such a root location would include a drive letter,
/// a web server name, an assembly name, etc.
/// </p>
/// </remarks>
/// <value>
/// The root location of the resource.
/// </value>
/// <exception cref="System.NotSupportedException">
/// This, the default implementation, <b>always</b> throws a
/// <see cref="System.NotSupportedException"/>.
/// </exception>
protected virtual string RootLocation
{
get { throw new NotSupportedException(); }
}
/// <summary>
/// Gets the current path of the resource.
/// </summary>
/// <remarks>
/// <p>
/// An example value of this property would be the name of the
/// directory containing a filesystem based resource.
/// </p>
/// </remarks>
/// <value>
/// The current path of the resource.
/// </value>
/// <exception cref="System.NotSupportedException">
/// This, the default implementation, <b>always</b> throws a
/// <see cref="System.NotSupportedException"/>.
/// </exception>
protected virtual string ResourcePath
{
get { throw new NotSupportedException(); }
}
/// <summary>
/// Gets those characters that are valid path separators for the
/// resource type.
/// </summary>
/// <remarks>
/// <p>
/// An example value of this property would be the
/// <see cref="System.IO.Path.DirectorySeparatorChar"/> and
/// <see cref="System.IO.Path.AltDirectorySeparatorChar"/> values for a
/// filesystem based resource.
/// </p>
/// <p>
/// Any derived classes that override this method are expected to
/// return a new array for each access of this property.
/// </p>
/// </remarks>
/// <value>
/// Those characters that are valid path separators for the resource
/// type.
/// </value>
/// <exception cref="System.NotSupportedException">
/// This, the default implementation, <b>always</b> throws a
/// <see cref="System.NotSupportedException"/>.
/// </exception>
protected virtual char[] PathSeparatorChars
{
get { throw new NotSupportedException(); }
}
/// <summary>
/// Does the supplied <paramref name="resourceName"/> relative ?
/// </summary>
/// <param name="resourceName">
/// The name of the resource to test.
/// </param>
/// <returns>
/// <see langword="true"/> if resource name is relative;
/// otherwise <see langword="false"/>.
/// </returns>
protected virtual bool IsRelativeResource(string resourceName)
{
return false;
}
/// <summary>
/// Creates a new resource that is relative to this resource based on the
/// supplied <paramref name="resourceName"/>.
/// </summary>
/// <remarks>
/// <p>
/// This method can accept either a fully qualified resource name or a
/// relative resource name as it's parameter.
/// </p>
/// <p>
/// A fully qualified resource is one that has a protocol prefix and
/// all elements of the resource name. All other resources are treated
/// as relative to this resource, and the following rules are used to
/// locate a relative resource:
/// </p>
/// <list type="bullet">
/// <item>
/// If the <paramref name="resourceName"/> starts with <c>'..'</c>,
/// the current resource path is navigated backwards before the
/// <paramref name="resourceName"/> is concatenated to the current
/// <see cref="Spring.Core.IO.AbstractResource.ResourcePath"/> of
/// this resource.
/// </item>
/// <item>
/// If the <paramref name="resourceName"/> starts with '/', the
/// current resource path is ignored and a new resource name is
/// appended to the
/// <see cref="Spring.Core.IO.AbstractResource.RootLocation"/> of
/// this resource.
/// </item>
/// <item>
/// If the <paramref name="resourceName"/> starts with '.' or a
/// letter, a new path is appended to the current
/// <see cref="Spring.Core.IO.AbstractResource.ResourcePath"/> of
/// this resource.
/// </item>
/// </list>
/// </remarks>
/// <param name="resourceName">
/// The name of the resource to create.
/// </param>
/// <returns>The relative resource.</returns>
/// <exception cref="System.UriFormatException">
/// If the process of resolving the relative resource yielded an
/// invalid URI.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// If this resource does not support the resolution of relative
/// resources (as determined by the value of the
/// <see cref="Spring.Core.IO.AbstractResource.SupportsRelativeResources"/>
/// property).
/// </exception>
/// <seealso cref="Spring.Core.IO.AbstractResource.ResourcePath"/>
public virtual IResource CreateRelative(string resourceName)
{
AssertUtils.ArgumentNotNull(resourceName, "relativePath");
// try to create fully qualified resource...
IResourceLoader loader = GetResourceLoader();
if (ConfigurableResourceLoader.HasProtocol(resourceName))
{
IResource resource = loader.GetResource(resourceName);
if (resource != null)
{
return resource;
}
}
if (!SupportsRelativeResources)
{
throw new NotSupportedException(GetType().Name +
" does not support relative resources. Please use fully qualified resource name.");
}
StringBuilder fullResourceName = new StringBuilder(256);
if (Protocol != null && Protocol != String.Empty)
{
fullResourceName.Append(Protocol).Append(ConfigurableResourceLoader.ProtocolSeparator);
}
if (!IsRelativeResource(resourceName))
{
fullResourceName.Append(resourceName);
}
else
{
string targetResource;
string resourcePath;
int n = resourceName.LastIndexOfAny(new char[] { '/', '\\' });
if (n >= 0)
{
targetResource = resourceName.Substring(n + 1);
resourcePath = CalculateResourcePath(resourceName.Substring(0, n + 1));
}
else // only resource name is specified, so current path should be used
{
targetResource = resourceName;
resourcePath = ResourcePath;
}
fullResourceName.Append(RootLocation.TrimEnd('\\','/'));
if (resourcePath != null && resourcePath != String.Empty)
{
fullResourceName.Append('/').Append(resourcePath);
}
fullResourceName.Append('/').Append(targetResource);
}
string resultResourceName = fullResourceName.ToString();
if (!ConfigurableResourceLoader.HasProtocol( resultResourceName ))
{
// give derived resource classes a chance to create an instance on their own
IResource resultResource = CreateResourceInstance( resultResourceName );
if (resultResource != null) return resultResource;
}
// create resource instance using default loader
return loader.GetResource(resultResourceName);
}
/// <summary>
/// Calculates a new resource path based on the supplied
/// <paramref name="relativePath"/>.
/// </summary>
/// <param name="relativePath">
/// The relative path to evaluate.
/// </param>
/// <returns>The newly calculated resource path.</returns>
private string CalculateResourcePath(string relativePath )
{
StringBuilder path = new StringBuilder(256);
if (relativePath.StartsWith("..")) // back level navigation
{
string[] pathElements = ResourcePath.Split(PathSeparatorChars);
int upWalks = UpWalks(relativePath);
if (upWalks > pathElements.Length)
{
throw new UriFormatException("Too many back levels.");
}
char separator = PathSeparatorChars[0];
for (int i = 0; i < pathElements.Length - upWalks; i++)
{
path.Append(pathElements[i]).Append(separator);
}
string[] relativeParts = relativePath.Split('/', '\\');
for (int i = upWalks; i < relativeParts.Length - 1; i++)
{
path.Append(relativeParts[i]).Append(separator);
}
if (path.Length > 0)
{
path.Length -= 1;
}
return path.ToString();
}
else if (relativePath.StartsWith("/")) // relative to root
{
if (relativePath.Length > 1)
{
return relativePath.Substring(1, relativePath.Length - 2);
}
else
{
return null;
}
}
else // relative to current namespace...
{
if (ResourcePath != null && ResourcePath != String.Empty)
{
path.Append(ResourcePath.TrimEnd(PathSeparatorChars)).Append(PathSeparatorChars[0]);
}
if (relativePath.StartsWith("./"))
{
if (relativePath.Length > 2)
{
path.Append(relativePath.Substring(2, relativePath.Length - 3));
}
}
else
{
path.Append(relativePath.Substring(0, relativePath.Length - 1));
}
return path.ToString();
}
}
private int UpWalks(string path)
{
string[] parts = path.Split('/', '\\');
int count = 0;
for (; count < parts.Length && parts[count].Equals(".."); count++)
{
;
}
return count;
}
#endregion
}
}
|