#line 1
package PPI::Cache;

#line 46

use strict;
use Carp          ();
use File::Spec    ();
use File::Path    ();
use Storable      ();
use Digest::MD5   ();
use Params::Util  qw{_INSTANCE _SCALAR};
use PPI::Document ();

use vars qw{$VERSION};
BEGIN {
	$VERSION = '1.215';
}

use constant VMS => !! ( $^O eq 'VMS' );

sub import {
	my $class = ref $_[0] ? ref shift : shift;
	return 1 unless @_;

	# Create a cache from the params provided
	my $cache = $class->new(@_);

	# Make PPI::Document use it
	unless ( PPI::Document->set_cache( $cache ) ) {
		Carp::croak("Failed to set cache in PPI::Document");
	}

	1;
}





#####################################################################
# Constructor and Accessors

#line 113

sub new {
	my $class  = shift;
	my %params = @_;

	# Path should exist and be usable
	my $path = $params{path}
		or Carp::croak("Cannot create PPI::Cache, no path provided");
	unless ( -d $path ) {
		Carp::croak("Cannot create PPI::Cache, path does not exist");
	}
	unless ( -r $path and -x $path ) {
		Carp::croak("Cannot create PPI::Cache, no read permissions for path");
	}
	if ( ! $params{readonly} and ! -w $path ) {
		Carp::croak("Cannot create PPI::Cache, no write permissions for path");
	}

	# Create the basic object
	my $self = bless {
		path     => $path,
		readonly => !! $params{readonly},
	}, $class;

	$self;
}

#line 148

sub path { $_[0]->{path} }

#line 159

sub readonly { $_[0]->{readonly} }





#####################################################################
# PPI::Cache Methods

#line 177

sub get_document {
	my $self = ref $_[0]
		? shift
		: Carp::croak('PPI::Cache::get_document called as static method');
	my $md5hex = $self->_md5hex(shift) or return undef;
	$self->_load($md5hex);
}

#line 198

sub store_document {
	my $self     = shift;
	my $Document = _INSTANCE(shift, 'PPI::Document') or return undef;

	# Shortcut if we are readonly
	return 1 if $self->readonly;

	# Find the filename to save to
	my $md5hex = $Document->hex_id or return undef;

	# Store the file
	$self->_store( $md5hex, $Document );
}





#####################################################################
# Support Methods

# Store an arbitrary PPI::Document object (using Storable) to a particular
# path within the cache filesystem.
sub _store {
	my ($self, $md5hex, $object) = @_;
	my ($dir, $file) = $self->_paths($md5hex);

	# Save the file
	File::Path::mkpath( $dir, 0, 0755 ) unless -d $dir;
	if ( VMS ) {
		Storable::lock_nstore( $object, $file );
	} else {
		Storable::nstore( $object, $file );
	}
}

# Load an arbitrary object (using Storable) from a particular
# path within the cache filesystem.
sub _load {
	my ($self, $md5hex) = @_;
	my (undef, $file) = $self->_paths($md5hex);

	# Load the file
	return '' unless -f $file;
	my $object = VMS
		? Storable::retrieve( $file )
		: Storable::lock_retrieve( $file );

	# Security check
	unless ( _INSTANCE($object, 'PPI::Document') ) {
		Carp::croak("Security Violation: Object in '$file' is not a PPI::Document");
	}

	$object;
}

# Convert a md5 to a dir and file name
sub _paths {
	my $self   = shift;
	my $md5hex = lc shift;
	my $dir    = File::Spec->catdir( $self->path, substr($md5hex, 0, 1), substr($md5hex, 0, 2) );
	my $file   = File::Spec->catfile( $dir, $md5hex . '.ppi' );
	return ($dir, $file);
}

# Check a md5hex param
sub _md5hex {
	my $either = shift;
	my $it     = _SCALAR($_[0])
		? PPI::Util::md5hex(${$_[0]})
		: $_[0];
	return (defined $it and ! ref $it and $it =~ /^[a-f0-9]{32}\z/si)
		? lc $it
		: undef;
}

1;

#line 304
