package com.mindprod.htmlmacros;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.CRC32;
/**
* Select a "random" quotation from a collection of quotations.
* <p/>
* Selection is based on the time and the digest of the name of the file where the quotation is being inserted and where on the page it is being inserted.
* <p/>
* It is not really random. It is completely reproducible.
* <p/>
* This is not a Macro. It is a collection of methods to help select random quotations, random advertisement,
* PSAs, that change periodically. The selections are not random. They produce the same results every time
* the code is run. This allows choices to remain stable for weeks at a time. There is nothing specific to
* ads or quotations in this class.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.8 2009-02-06 include go package in ZIP bundle.
* @since 2009
*/
class Randomiser
{
/**
* true if debugging to get extra output
*/
private static final boolean DEBUGGING = false;
/**
* how many milliseconds in a day
*/
private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000L;
/**
* how many milliseconds in an hour
*/
private static final long MILLIS_PER_HOUR = 60 * 60 * 1000L;
/**
* previous fileBeingProcessed
*/
private static File prevFileBeingProcessed;
/**
* which invocation on the page is this, 0 is first 1 is second etc.
*/
private static int sequenceOnPage;
/**
* Calc how frequently change ANY ad/quote, (more frequently than any single ad)
*
* @param changeIntervalInMillis is how often we change any given ad/quote
*
* @return rotateIntervalInMillis
*/
private static long calcRotateInterval( long changeIntervalInMillis )
{
if ( changeIntervalInMillis >= MILLIS_PER_DAY )
{
return MILLIS_PER_DAY;
}
else if ( changeIntervalInMillis >= MILLIS_PER_HOUR * 2 )
{
return MILLIS_PER_HOUR * 2;
}
else if ( changeIntervalInMillis >= MILLIS_PER_HOUR )
{
return MILLIS_PER_HOUR;
}
else
{
return changeIntervalInMillis;
}
}
/**
* Assign a random ad OR quotation to a file, by producing a selectorIndex
*
* @param changeIntervalInMillis how often to change the ad or quotation.
* @param fileBeingProcessed the file currently being processed.
*
* @return random index 0..possibilitiies-1, -1 if possibilities == 0
*/
static int getHashForFile( final long changeIntervalInMillis, final File fileBeingProcessed )
{
try
{
if ( fileBeingProcessed.equals( prevFileBeingProcessed ) )
{
sequenceOnPage++;
}
else
{
sequenceOnPage = 0;
prevFileBeingProcessed = fileBeingProcessed;
}
final MessageDigest digester1 = MessageDigest.getInstance( "SHA" );
final long rotateIntervalInMillis = calcRotateInterval( changeIntervalInMillis );
final long accelerateChangeByMillis = ( ( fileBeingProcessed.hashCode() & Integer.MAX_VALUE )
% ( changeIntervalInMillis / rotateIntervalInMillis ) ) * rotateIntervalInMillis;
final int seed = ( int ) ( ( System.currentTimeMillis() + accelerateChangeByMillis ) / changeIntervalInMillis );
for ( int shift = 24; shift >= 0; shift -= 8 )
{
digester1.update( ( byte ) ( ( seed >>> shift ) & 0xff ) );
}
digester1.update( fileBeingProcessed.getPath().getBytes( "UTF-8" ) );
digester1.update( ( byte ) ( sequenceOnPage * 41 ) );
final byte[] digest = digester1.digest();
assert digest.length == 20 : "SHA digest not 20 bytes long";
CRC32 digester2 = new CRC32();
digester2.update( digest );
return ( int ) digester2.getValue();
}
catch ( UnsupportedEncodingException e )
{
throw new IllegalArgumentException( "Missing UTF-8 encoding support" );
}
catch ( NoSuchAlgorithmException e )
{
throw new IllegalArgumentException( "Missing SHA support" );
}
}
/**
* given a 32-bit hash/digest, returns an a selection number.
*
* @param hash 32 bits, possibly signed.
* @param possibilities how many possible choices there are
*
* @return a repeatable number in the range 0..possibilities.-1, If possibilities is 0, returns -1.
*/
@SuppressWarnings( { "WeakerAccess" } )
static int getRandomSelectorIndexForHash( final int hash, final int possibilities )
{
if ( possibilities == 0 )
{
return -1;
}
return ( hash & Integer.MAX_VALUE ) % possibilities;
}
/**
* given a 32-bit hash/digest, returns an a selection number.
*
* @param hash 32 bits, possibly signed.
* @param weights weight for choices 0..n-1. Not necessarily normalised to 100.
*
* @return a repeatable number in the range 0..n-1 based on hash.
*/
@SuppressWarnings( { "WeakerAccess" } )
static int getWeightedSelectorIndexForHash( final int hash, final int... weights )
{
int sum1 = 0;
for ( int weight : weights )
{
sum1 += weight;
}
final int cutoff = ( hash & Integer.MAX_VALUE ) % sum1;
int sum2 = 0;
for ( int choice = 0; choice < weights.length; choice++ )
{
sum2 += weights[ choice ];
if ( sum2 > cutoff )
{
return choice;
}
}
assert false : "program should never get here";
return weights.length - 1;
}
}