The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* BLECH!!!! needed for HvMROMETA */
#define PERL_CORE
#include "EXTERN.h"
#include "perl.h"
#undef PERL_CORE
#include "XSUB.h"

#define NEED_sv_2pv_flags
#include "ppport.h"

#ifdef HvMROMETA
	#define HvCURGEN(stash) ( HvMROMETA(stash)->cache_gen + PL_sub_generation )
#else
	#define HvCURGEN(stash) PL_sub_generation
#endif

#ifndef GvCV_set
#  define GvCV_set(gv, cv)  GvCV(gv) = cv
#endif

STATIC GV *sv_gv(SV *sv) {
	if ( sv ) {
		if ( SvROK(sv) )
			sv = SvRV(sv);

		if ( SvTYPE(sv) == SVt_PVGV ) {
			return (GV *)sv;
		} else if ( SvPOK(sv) ) {
			/* fully qualified name case */
			/* OMIGAWD XMATH UR TEH GREATES KTHX FR RITING THIS!!! COPYRAIT */
			GV** gvp;
			char *s, *end = NULL, saved;
			char *name = SvPV_nolen(sv);
			HV *stash = CopSTASH(PL_curcop);

			for (s = name; *s++; ) {
				if (*s == ':' && s[-1] == ':')
					end = ++s;
				else if (*s && s[-1] == '\'')
					end = s;
			}
			s--;
			if (end) {
				saved = *end;
				*end = 0;
				stash = GvHV(gv_fetchpv(name, TRUE, SVt_PVHV));
				*end = saved;
				name = end;
			}

			gvp = (GV**)hv_fetch(stash, name, s - name, 1);

			if(gvp) {
				GV *gv = *gvp;
				if (SvTYPE(gv) != SVt_PVGV)
					gv_init(gv, stash, name, s - name, TRUE);

				return gv;
			}
		}
	}

	Perl_croak(aTHX_ "Must provide a glob ref");
}

STATIC HV *sv_stash (SV *sv) {
	HV *hv;
	if ( SvROK(sv) ) {
			SV *rv = SvRV(sv);
			if ( sv_isobject(rv) ) {
				return SvSTASH(rv);
			} else {
				if ( SvTYPE(rv) == SVt_PVHV ) {
					hv = (HV *)rv;
					if ( HvNAME(hv) )
						return hv;
				}
				/* if SVt_PVGV maybe try e.g. *Foo for "Foo" ? */
			}
	} else if ( SvOK(sv) ) {
		return gv_stashsv(sv, 0);
	}

	Perl_croak(aTHX_ "Must provide a class name");
}


MODULE = Class::MethodCache	PACKAGE = Class::MethodCache

U32
get_class_gen (sv)
	INPUT:
		SV *sv
	PREINIT:
		HV *stash = sv_stash(sv);
	CODE:
		RETVAL = HvCURGEN(stash);
	OUTPUT: RETVAL

void
update_cvgen (sv)
	INPUT:
		SV *sv
	PREINIT:
		GV *gv = sv_gv(sv);
	CODE:
		if ( GvCVGEN(gv) )
			GvCVGEN(gv) = HvCURGEN(GvSTASH(gv));
		else
			Perl_croak(aTHX_ "Won't update cvgen for real method.");

void
delete_cv (sv)
	INPUT:
		SV *sv
	PREINIT:
		GV *gv = sv_gv(sv);
	CODE:
		if ( GvCV(gv) )
			SvREFCNT_dec(GvCV(gv));
		GvCV_set(gv, NULL);
		GvCVGEN(gv) = 0;

SV *
get_cached_method (sv)
	INPUT:
		SV *sv
	PREINIT:
		GV *gv = sv_gv(sv);
	PPCODE:
		if ( GvCV(gv) && GvCVGEN(gv) == HvCURGEN(GvSTASH(gv)) )
			XPUSHs(sv_2mortal(newRV_inc((SV *)GvCV(gv))));
		else
			XPUSHs(&PL_sv_undef);

void
set_cached_method (sv, cv_sv)
	INPUT:
		SV *sv
		SV *cv_sv
	PREINIT:
		GV *gv = sv_gv(sv);
		CV *cv = SvROK(cv_sv) ? (CV *)SvRV(cv_sv) : NULL;
	CODE:
		if ( !cv || SvTYPE(cv) != SVt_PVCV )
			Perl_croak(aTHX_ "cv is not a code reference");

		if ( GvREFCNT(gv) == 1 ) {
			if ( GvCV(gv) ) {
				if ( GvCVGEN(gv) == 0 )
					Perl_croak(aTHX_ "Won't overwrite real method.");
				SvREFCNT_dec(GvCV(gv));
			}
			SvREFCNT_inc(cv);
			GvCV_set(gv, cv);
			GvCVGEN(gv) = HvCURGEN(GvSTASH(gv));
		} else {
			Perl_croak(aTHX_ "Setting a cached method in a cached GV might cause strange things to happen.");
		}

SV *
get_cv (sv)
	INPUT:
		SV *sv
	PREINIT:
		GV *gv = sv_gv(sv);
	PPCODE:
		if ( GvCV(gv) )
			XPUSHs(sv_2mortal(newRV_inc((SV *)GvCV(gv))));
		else
			XPUSHs(&PL_sv_undef);

SV *
set_cv (sv, cv_sv)
	INPUT:
		SV *sv
		SV *cv_sv
	PREINIT:
		CV *cv;
		GV *gv = sv_gv(sv);
	PPCODE:
		if ( !SvOK(cv_sv) ) {
			cv = NULL;
		} else if ( SvROK(cv_sv) && SvTYPE(SvRV(cv_sv)) == SVt_PVCV ) {
			cv = (CV *)SvRV(cv_sv);
			SvREFCNT_inc(cv);
		} else {
			Perl_croak(aTHX_ "set_cv accepts either a code reference or undef");
		}

		if ( GvCV(gv) )
			SvREFCNT_dec(GvCV(gv));
		GvCV_set(gv, cv);


U32
get_gv_refcount (sv)
	INPUT:
		SV *sv
	PREINIT:
		GV *gv = sv_gv(sv);
	CODE:
		RETVAL = GvREFCNT(gv); /* refcount of the GP, not the GV */
	OUTPUT: RETVAL

void
set_cvgen (sv, gen)
	INPUT:
		SV *sv
		U32 gen
	PREINIT:
		GV *gv = sv_gv(sv);
	CODE:
		GvCVGEN(gv) = gen;

U32
get_cvgen (sv)
	INPUT:
		SV *sv
	PREINIT:
		GV *gv = sv_gv(sv);
	CODE:
		RETVAL = GvCVGEN(gv);
	OUTPUT: RETVAL

void
mro_isa_changed_in (sv)
	INPUT:
		SV *sv
	PREINIT:
		HV *stash = sv_stash(sv);
	CODE:
#ifdef mro_isa_changed_in
		mro_isa_changed_in(stash);
#else
		PL_sub_generation++;
#endif