org.refcodes.codec.BaseBuilder Maven / Gradle / Ivy
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.codec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.refcodes.codec.BaseMetricsAccessor.BaseMetricsBuilder;
import org.refcodes.codec.BaseMetricsAccessor.BaseMetricsProperty;
import org.refcodes.data.Binary;
import org.refcodes.data.BitMask;
import org.refcodes.exception.RuntimeIOException;
import org.refcodes.io.ByteArrayReceiver;
import org.refcodes.io.ByteArraySource;
import org.refcodes.numerical.NumericalUtility;
/**
* The {@link BaseBuilder} provides the functionality to do base encoding and
* decoding such as done by the Base64 encoding and decoding functionality (see
* "https://en.wikipedia.org/wiki/Base64"). The {@link BaseBuilder} makes use of
* the utility-Builder-Pattern and is designed to support codes starting with
* Base2 till Base64 and further up.
*/
public class BaseBuilder implements BaseMetricsProperty, BaseMetricsBuilder {
// /////////////////////////////////////////////////////////////////////////
// CONSTANTS:
// /////////////////////////////////////////////////////////////////////////
private static final boolean IS_USE_SINGLE_CODE_BASE = false;
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
private BaseMetrics _baseCodecMetrics = null;
private String _encodedText = null;
private byte[] _decodedData = null;
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public BaseBuilder withBaseMetrics( BaseMetrics aBaseMetricsCodec ) {
setBaseMetrics( aBaseMetricsCodec );
return this;
}
/**
* Sets the number base for the number base property.
*
* @param aNumberBase The number base to be stored by the base codec metrics
* property.
*/
public void setBaseMetrics( int aNumberBase ) {
setBaseMetrics( BaseMetricsConfig.toBaseCodec( aNumberBase ) );
}
/**
* Sets the number base for the number base property.
*
* @param aNumberBase The number base to be stored by the base codec metrics
* property.
*
* @return The builder for applying multiple build operations.
*/
public BaseBuilder withBaseMetrics( int aNumberBase ) {
setBaseMetrics( aNumberBase );
return this;
}
/**
* Retrieves the encoded text calculated from the decoded data. This method
* is to be side effect free in terms of the decoded data (and the encoded
* result) is not part of the state for this instance (from the point of
* view of this method). Still changing for example the {@link BaseMetrics}
* via {@link #withBaseMetrics(BaseMetrics)} can cause side effects! For
* avoiding thread race conditions / side effects regarding the decoded data
* (and the encoded result), use this method instead of the combination of
* {@link #withDecodedData(byte[])} with {@link #getEncodedText()}
*
* @param aDecodedData The decoded data to be encoded.
*
* @return The encoded text calculated from the decoded data.
*/
public String toEncodedText( String aDecodedData ) {
return toEncodedText( aDecodedData.getBytes() );
}
/**
* Retrieves the encoded text calculated from the decoded data. This method
* is to be side effect free in terms of the decoded data (and the encoded
* result) is not part of the state for this instance (from the point of
* view of this method). Still changing for example the {@link BaseMetrics}
* via {@link #withBaseMetrics(BaseMetrics)} can cause side effects! For
* avoiding thread race conditions / side effects regarding the decoded data
* (and the encoded result), use this method instead of the combination of
* {@link #withDecodedData(byte[])} with {@link #getEncodedText()}
*
* @param aDecodedData The decoded data to be encoded.
* @param aCharset The charset to use when interpreting the decoded data
* string.
*
* @return The encoded text calculated from the decoded data.
*/
public String toEncodedText( String aDecodedData, Charset aCharset ) {
return toEncodedText( aDecodedData.getBytes( aCharset ) );
}
/**
* Retrieves the encoded text calculated from the decoded data. This method
* is to be side effect free in terms of the decoded data (and the encoded
* result) is not part of the state for this instance (from the point of
* view of this method). Still changing for example the {@link BaseMetrics}
* via {@link #withBaseMetrics(BaseMetrics)} can cause side effects! For
* avoiding thread race conditions / side effects regarding the decoded data
* (and the encoded result), use this method instead of the combination of
* {@link #withDecodedData(byte[])} with {@link #getEncodedText()}
*
* @param aDecodedData The decoded data to be encoded.
* @param aCharset The charset to use when interpreting the decoded data
* string.
*
* @return The encoded text calculated from the decoded data.
*
* @throws UnsupportedEncodingException thrown in case the provided charset
* name is not supported.
*/
public String toEncodedText( String aDecodedData, String aCharset ) throws UnsupportedEncodingException {
return toEncodedText( aDecodedData.getBytes( aCharset ) );
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
*/
public void setDecodedData( String aDecodedData ) {
setDecodedData( aDecodedData.getBytes() );
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
* @param aCharset The charset to use when interpreting the decoded data
* string.
*/
public void setDecodedData( String aDecodedData, Charset aCharset ) {
setDecodedData( aDecodedData.getBytes( aCharset ) );
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
* @param aCharset The charset to use when interpreting the decoded data
* string.
*
* @throws UnsupportedEncodingException thrown in case the provided charset
* name is not supported.
*/
public void setDecodedData( String aDecodedData, String aCharset ) throws UnsupportedEncodingException {
setDecodedData( aDecodedData.getBytes( aCharset ) );
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
*
* @return The builder for applying multiple build operations.
*/
public BaseBuilder withDecodedData( byte[] aDecodedData ) {
setDecodedData( aDecodedData );
return this;
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
*
* @return The builder for applying multiple build operations.
*/
public BaseBuilder withDecodedData( String aDecodedData ) {
setDecodedData( aDecodedData );
return this;
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
* @param aCharset The charset to use when interpreting the decoded data
* string.
*
* @return The builder for applying multiple build operations.
*/
public BaseBuilder withDecodedData( String aDecodedData, Charset aCharset ) {
setDecodedData( aDecodedData, aCharset );
return this;
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
* @param aCharset The charset to use when interpreting the decoded data
* string.
*
* @return The builder for applying multiple build operations.
*
* @throws UnsupportedEncodingException thrown in case the provided charset
* name is not supported.
*/
public BaseBuilder withDecodedData( String aDecodedData, String aCharset ) throws UnsupportedEncodingException {
setDecodedData( aDecodedData, aCharset );
return this;
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
*
* @return The builder for applying multiple build operations.
*/
public BaseBuilder withDecodedData( long aDecodedData ) {
setDecodedData( aDecodedData );
return this;
}
/**
* {@inheritDoc}
*/
@Override
public BaseMetrics getBaseMetrics() {
return _baseCodecMetrics;
}
/**
* {@inheritDoc}
*/
@Override
public void setBaseMetrics( BaseMetrics aBaseMetrics ) {
_baseCodecMetrics = aBaseMetrics;
}
/**
* Retrieves the encoded text from the encoded text property.
*
* @return The encoded text stored by the encoded text property.
*/
public String getEncodedText() {
if ( IS_USE_SINGLE_CODE_BASE ) {
_encodedText = toEncodedText( _decodedData );
}
else {
_encodedText = toEncodedText( _decodedData, _baseCodecMetrics );
}
_decodedData = null;
return _encodedText;
}
/**
* Sets the encoded text for the encoded text property.
*
* @param aEncodedText The encoded text to be stored by the encoded text
* property.
*/
public void setEncodedText( String aEncodedText ) {
_encodedText = aEncodedText;
_decodedData = null;
}
/**
* Sets the encoded text for the encoded text property.
*
* @param aEncodedText The encoded text to be stored by the encoded text
* property.
*
* @return The builder for applying multiple build operations.
*/
public BaseBuilder withEncodedText( String aEncodedText ) {
setEncodedText( aEncodedText );
return this;
}
/**
* Retrieves the decoded data from the decoded data property.
*
* @return The decoded data stored by the decoded data property.
*/
public byte[] getDecodedData() {
if ( IS_USE_SINGLE_CODE_BASE ) {
_decodedData = toDecodedData( _encodedText );
}
else {
_decodedData = toDecodedData( _encodedText, _baseCodecMetrics );
}
_encodedText = null;
return _decodedData;
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
*/
public void setDecodedData( byte[] aDecodedData ) {
_decodedData = aDecodedData;
_encodedText = null;
}
/**
* Sets the decoded data for the decoded data property.
*
* @param aDecodedData The decoded data to be stored by the decoded data
* property.
*/
public void setDecodedData( long aDecodedData ) {
setDecodedData( NumericalUtility.toBytes( aDecodedData ) );
}
/**
* Retrieves the encoded text calculated from the decoded data. This method
* is to be side effect free in terms of the decoded data (and the encoded
* result) is not part of the state for this instance (from the point of
* view of this method). Still changing for example the {@link BaseMetrics}
* via {@link #withBaseMetrics(BaseMetrics)} can cause side effects! For
* avoiding thread race conditions / side effects regarding the decoded data
* (and the encoded result), use this method instead of the combination of
* {@link #withDecodedData(byte[])} with {@link #getEncodedText()}
*
* @param aDecodedData The decoded data to be encoded.
*
* @return The encoded text calculated from the decoded data.
*/
public String toEncodedText( byte[] aDecodedData ) {
if ( IS_USE_SINGLE_CODE_BASE ) {
final ByteArraySource theConsumer = new ByteArraySource();
final BaseEncoder theEncoder = new BaseEncoder( theConsumer ).withBaseMetrics( _baseCodecMetrics );
try {
theEncoder.transmitBytes( aDecodedData );
theEncoder.close();
}
catch ( IOException e ) {
throw new RuntimeIOException( e );
}
return new String( theConsumer.getBytes() );
}
else {
return toEncodedText( aDecodedData, _baseCodecMetrics );
}
}
/**
* Retrieves the decoded data calculated from the provided encoded text.
* This method is to be side effect free in terms of the encoded text (and
* the decoded result) is not part of the state for this instance (from the
* point of view of this method). Still changing for example the
* {@link BaseMetrics} via {@link #withBaseMetrics(BaseMetrics)} can cause
* side effects! For avoiding thread race conditions / side effects
* regarding the encoded text (and the decoded result), use this method
* instead of the combination of {@link #withEncodedText(String)} with
* {@link #getDecodedData()}.
*
* @param aEncodedText The encoded text to be decoded.
*
* @return The decoded data decoded from the encoded text.
*/
public byte[] toDecodedData( String aEncodedText ) {
aEncodedText = aEncodedText.replaceAll( "\n", "" );
if ( IS_USE_SINGLE_CODE_BASE ) {
final ByteArrayReceiver theProvider = new ByteArrayReceiver( aEncodedText.getBytes() );
try {
final BaseDecoder theDecoder = new BaseDecoder( theProvider ).withBaseMetrics( _baseCodecMetrics );
final List theDecodedBytes = new ArrayList<>();
byte[] eDecodedBytes;
while ( theDecoder.hasAvailable() ) {
eDecodedBytes = theDecoder.receiveAllBytes();
for ( Byte eByte : eDecodedBytes ) {
theDecodedBytes.add( eByte );
}
}
theDecoder.close();
return toPrimitiveType( theDecodedBytes.toArray( new Byte[theDecodedBytes.size()] ) );
}
catch ( IOException e ) {
throw new RuntimeIOException( e );
}
}
else {
return toDecodedData( aEncodedText, _baseCodecMetrics );
}
}
/**
* Retrieves the encoded text calculated from the decoded data. This method
* is to be side effect free in terms of the decoded data (and the encoded
* result) is not part of the state for this instance (from the point of
* view of this method). Still changing for example the {@link BaseMetrics}
* via {@link #withBaseMetrics(BaseMetrics)} can cause side effects! For
* avoiding thread race conditions / side effects regarding the decoded data
* (and the encoded result), use this method instead of the combination of
* {@link #withDecodedData(byte[])} with {@link #getEncodedText()}
*
* @param aDecodedData The decoded data to be encoded.
*
* @return The encoded text calculated from the decoded data.
*/
public String toEncodedText( long aDecodedData ) {
return toEncodedText( NumericalUtility.toBytes( aDecodedData ) );
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
// -------------------------------------------------------------------------
// ENCODING:
// -------------------------------------------------------------------------
/**
* To encoded text.
*
* @param aDecodedData the decoded data
* @param aBaseMetrics the base metrics
*
* @return the string
*/
protected static String toEncodedText( byte[] aDecodedData, BaseMetrics aBaseMetrics ) {
final int theMod = aDecodedData.length % aBaseMetrics.getBytesPerInt();
int theTrailingBytes = 0;
final int theEncodedSize = toEncodedSize( aDecodedData, aBaseMetrics );
final char[] theEncodedText = new char[theEncodedSize];
int theIndex = 0;
int eTrailingUnused;
for ( int theOffset = 0; theOffset < aDecodedData.length; theOffset += aBaseMetrics.getBytesPerInt() ) {
int eWord = toWord( aDecodedData, theOffset, aBaseMetrics );
if ( theOffset + aBaseMetrics.getBytesPerInt() >= aDecodedData.length ) {
theTrailingBytes = ( theMod == 0 ) ? 0 : aBaseMetrics.getBytesPerInt() - theMod;
}
eTrailingUnused = Binary.BITS_PER_BYTE.getValue() * ( Binary.BYTES_PER_INT.getValue() - aBaseMetrics.getBytesPerInt() );
eWord <<= eTrailingUnused;
for ( int i = 0; i < aBaseMetrics.getDigitsPerInt() - theTrailingBytes; i++ ) {
final int eByte = ( eWord >> ( Binary.BITS_PER_INT.getValue() - aBaseMetrics.getBitsPerDigit() ) ) & aBaseMetrics.getDigitMask();
theEncodedText[theIndex++] = aBaseMetrics.toChar( eByte );
eWord <<= aBaseMetrics.getBitsPerDigit();
}
for ( int i = 0; i < theTrailingBytes; i++ ) {
theEncodedText[theIndex++] = aBaseMetrics.getPaddingChar();
}
}
final String theResult = new String( theEncodedText );
return theResult;
}
/**
* To encoded size.
*
* @param aDecodedData the decoded data
* @param aBaseMetrics the base metrics
*
* @return the int
*/
private static int toEncodedSize( byte[] aDecodedData, BaseMetrics aBaseMetrics ) {
final int theMod = aDecodedData.length % aBaseMetrics.getBytesPerInt();
final int theTrailingBytes = ( theMod == 0 ) ? 0 : aBaseMetrics.getBytesPerInt() - theMod;
final double d = ( (double) aBaseMetrics.getDigitsPerInt() ) / ( (double) aBaseMetrics.getBytesPerInt() );
final int theAdd = (int) Math.ceil( d * theTrailingBytes );
return aDecodedData.length * aBaseMetrics.getDigitsPerInt() / aBaseMetrics.getBytesPerInt() + theAdd;
}
/**
* To word.
*
* @param aDecodedData the decoded data
* @param aOffset the offset
* @param aBaseMetrics the base metrics
*
* @return the int
*/
private static int toWord( byte[] aDecodedData, int aOffset, BaseMetrics aBaseMetrics ) {
int eWord = 0;
for ( int i = 0; i < aBaseMetrics.getBytesPerInt(); i++ ) {
eWord <<= Binary.BITS_PER_BYTE.getValue();
if ( aOffset + i < aDecodedData.length ) {
eWord |= aDecodedData[aOffset + i] & BitMask.MASK_8.getValue();
}
}
return eWord;
}
// -------------------------------------------------------------------------
// DECODING:
// -------------------------------------------------------------------------
/**
* To decoded data.
*
* @param aEncodedText the encoded text
* @param aBaseMetrics the base metrics
*
* @return the byte[]
*/
protected static byte[] toDecodedData( String aEncodedText, BaseMetrics aBaseMetrics ) {
if ( aEncodedText.length() % aBaseMetrics.getDigitsPerInt() != 0 ) {
throw new IllegalArgumentException( "The length of <" + aEncodedText.length() + "> of the encoded text cannot be divided (modulo = 0) by <" + aBaseMetrics.getDigitsPerByte() + "> which is required by the codec <" + aBaseMetrics + ">." );
}
final int thePaddingIndex = aEncodedText.indexOf( aBaseMetrics.getPaddingChar() );
int theTrailingBytes = 0;
final int theDecodedSize = toDecodedSize( aEncodedText, aBaseMetrics );
final byte[] theDecodedData = new byte[theDecodedSize];
int theIndex = 0;
for ( int theOffset = 0; theOffset < aEncodedText.length(); theOffset += aBaseMetrics.getDigitsPerInt() ) {
if ( theOffset + aBaseMetrics.getDigitsPerInt() >= aEncodedText.length() ) {
theTrailingBytes = thePaddingIndex > 0 ? ( aEncodedText.length() - thePaddingIndex ) : 0;
}
int eWord = 0;
for ( int i = 0; i < aBaseMetrics.getDigitsPerInt(); i++ ) {
eWord <<= aBaseMetrics.getBitsPerDigit();
eWord |= aBaseMetrics.toValue( aEncodedText.charAt( theOffset + i ) ) & BitMask.MASK_8.getValue();
}
theIndex = toBytes( theDecodedData, theIndex, eWord, theTrailingBytes, aBaseMetrics );
}
return theDecodedData;
}
/**
* To decoded size.
*
* @param aEncodedText the encoded text
* @param aBaseMetrics the base metrics
*
* @return the int
*/
protected static int toDecodedSize( String aEncodedText, BaseMetrics aBaseMetrics ) {
final int thePaddingIndex = aEncodedText.indexOf( aBaseMetrics.getPaddingChar() );
final int theTrailingDigits = ( thePaddingIndex == -1 ) ? 0 : aEncodedText.length() - thePaddingIndex;
return aEncodedText.length() * aBaseMetrics.getBytesPerInt() / aBaseMetrics.getDigitsPerInt() - theTrailingDigits;
}
/**
* To bytes.
*
* @param aDecodedBytes the decoded bytes
* @param aOffset the offset
* @param aWord the word
* @param aTrailingBytes the trailing bytes
* @param aBaseMetrics the base metrics
*
* @return the int
*/
protected static int toBytes( byte[] aDecodedBytes, int aOffset, int aWord, int aTrailingBytes, BaseMetrics aBaseMetrics ) {
for ( int i = 0; i < aBaseMetrics.getBytesPerInt(); i++ ) {
if ( i >= aTrailingBytes ) {
final int theIndex = aOffset + aBaseMetrics.getBytesPerInt() - i - 1;
aDecodedBytes[theIndex] = (byte) aWord;
}
aWord >>= Binary.BITS_PER_BYTE.getValue();
}
return aOffset + aBaseMetrics.getBytesPerInt();
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
private static byte[] toPrimitiveType( Byte[] aBytes ) {
if ( aBytes == null ) {
return null;
}
final byte[] thePrimitives = new byte[aBytes.length];
for ( int i = 0; i < aBytes.length; i++ ) {
thePrimitives[i] = aBytes[i].byteValue();
}
return thePrimitives;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy