SAtom.java
/*
* Copyright 2017 Michael Mackenzie High
*
* 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.
*/
package com.mackenziehigh.sexpr;
import com.mackenziehigh.sexpr.internal.Escaper;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Symbolic Atom.
*
* <p>
* All instances of this interface are immutable.
* </p>
*/
public final class SAtom
implements Sexpr<SAtom>
{
/**
* This is the content() of this atom.
*/
private final String content;
/**
* This is the content() in a format that would be recognized by the parser.
*/
private final String parsableContent;
/**
* This is the location of this atom in an input-string,
* if this atom was obtained by parsing an input-string.
*/
private final SourceLocation location;
/**
* Cached Value.
*/
private Optional<Boolean> valueAsBoolean = Optional.empty();
/**
* Cached Value.
*/
private Optional<Character> valueAsChar = Optional.empty();
/**
* Cached Value.
*/
private Optional<Byte> valueAsByte = Optional.empty();
/**
* Cached Value.
*/
private Optional<Short> valueAsShort = Optional.empty();
/**
* Cached Value.
*/
private Optional<Integer> valueAsInt = Optional.empty();
/**
* Cached Value.
*/
private Optional<Long> valueAsLong = Optional.empty();
/**
* Cached Value.
*/
private Optional<Float> valueAsFloat = Optional.empty();
/**
* Cached Value.
*/
private Optional<Double> valueAsDouble = Optional.empty();
/**
* Cached Hash Code.
*/
private final int hash;
/**
* Constructor.
*
* @param value will be the content() of this atom.
* @param location will be the location() of this atom.
*/
private SAtom (final SourceLocation location,
final String value)
{
this.content = Objects.requireNonNull(value);
this.location = Objects.requireNonNull(location);
this.hash = value.hashCode();
this.parsableContent = createParsableContent();
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromBoolean (final boolean value)
{
return fromBoolean(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromChar (final char value)
{
return fromChar(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromByte (final byte value)
{
return fromByte(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromShort (final short value)
{
return fromShort(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromInt (final int value)
{
return fromInt(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromLong (final long value)
{
return fromLong(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromFloat (final float value)
{
return fromFloat(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromDouble (final double value)
{
return fromDouble(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromString (final String value)
{
return fromString(SourceLocation.DEFAULT, value);
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromBoolean (final SourceLocation location,
final boolean value)
{
return new SAtom(location, Boolean.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromChar (final SourceLocation location,
final char value)
{
return fromInt(location, (int) value);
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromByte (final SourceLocation location,
final byte value)
{
return new SAtom(location, Byte.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromShort (final SourceLocation location,
final short value)
{
return new SAtom(location, Short.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromInt (final SourceLocation location,
final int value)
{
return new SAtom(location, Integer.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromLong (final SourceLocation location,
final long value)
{
return new SAtom(location, Long.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromFloat (final SourceLocation location,
final float value)
{
return new SAtom(location, Float.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromDouble (final SourceLocation location,
final double value)
{
return new SAtom(location, Double.toString(value));
}
/**
* Factory Method.
*
* @param location will be the <code>location()</code> of the new atom.
* @param value will be the <code>content()</code> of the new atom.
* @return the new atom.
*/
public static SAtom fromString (final SourceLocation location,
final String value)
{
return new SAtom(location, value);
}
private String createParsableContent ()
{
/**
* If the content() contains whitespace, parentheses,
* at-symbols, single-quotes, double-quotes, or is empty,
* then escape and quote the string; otherwise,
* return the content() itself.
*/
if (content.isEmpty())
{
return "''";
}
else if (content.matches("[^\\s\\t\\r\\n()@'\"]+"))
{
return content();
}
else
{
return "'" + escaped() + "'";
}
}
/**
* This method retrieves the series of characters
* that this atom contains.
*
* @return the content of this atom.
*/
public String content ()
{
return content;
}
/**
* This method returns the content() with all
* special characters escaped.
*
* @return the escaped content().
*/
public String escaped ()
{
return Escaper.instance.escape(content().toCharArray());
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAtom ()
{
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isList ()
{
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean bfs (final Predicate<Sexpr<?>> condition)
{
return condition.test(this);
}
/**
* {@inheritDoc}
*/
@Override
public boolean dfs (final Predicate<Sexpr<?>> condition)
{
return condition.test(this);
}
/**
* {@inheritDoc}
*/
@Override
public boolean preorder (final Predicate<Sexpr<?>> condition)
{
return condition.test(this);
}
/**
* {@inheritDoc}
*/
@Override
public boolean postorder (final Predicate<Sexpr<?>> condition)
{
return condition.test(this);
}
/**
* {@inheritDoc}
*/
@Override
public void traverse (final Consumer<Sexpr<?>> before,
final Consumer<Sexpr<?>> after)
{
before.accept(this);
after.accept(this);
}
/**
* This method retrieves this value, as a boolean.
*
* <p>
* If toString() equals (ignoring case) "true", "yes", "on", "T", or "1",
* then this method will return a true result.
* </p>
*
* <p>
* If toString() equals (ignoring case) "false", "no", "off", "F", or "0",
* then this method will return a false result.
* </p>
*
* @return the value, if possible.
*/
public Optional<Boolean> asBoolean ()
{
if (valueAsBoolean.isPresent())
{
return valueAsBoolean;
}
switch (toString().toLowerCase())
{
case "true":
case "yes":
case "on":
case "t":
case "1":
return valueAsBoolean = Optional.of(Boolean.TRUE);
case "false":
case "no":
case "off":
case "f":
case "0":
return valueAsBoolean = Optional.of(Boolean.FALSE);
default:
return Optional.empty();
}
}
/**
* This method retrieves this value, as a char.
*
* @return the value, if possible.
*/
public Optional<Character> asChar ()
{
if (valueAsChar.isPresent())
{
return valueAsChar;
}
else if ("max".equalsIgnoreCase(content()))
{
return Optional.of(Character.MAX_VALUE);
}
else if ("maximum".equalsIgnoreCase(content()))
{
return Optional.of(Character.MAX_VALUE);
}
else if ("min".equalsIgnoreCase(content()))
{
return Optional.of(Character.MIN_VALUE);
}
else if ("minimum".equalsIgnoreCase(content()))
{
return Optional.of(Character.MIN_VALUE);
}
else if (asInt().isPresent() && asInt().get() >= Character.MIN_VALUE && asInt().get() <= Character.MAX_VALUE)
{
valueAsChar = Optional.of((char) (int) asInt().get());
return valueAsChar;
}
else
{
return Optional.empty();
}
}
/**
* This method retrieves this value, as a byte.
*
* @return the value, if possible.
*/
public Optional<Byte> asByte ()
{
if (valueAsByte.isPresent())
{
return valueAsByte;
}
else if ("max".equalsIgnoreCase(content()))
{
return Optional.of(Byte.MAX_VALUE);
}
else if ("maximum".equalsIgnoreCase(content()))
{
return Optional.of(Byte.MAX_VALUE);
}
else if ("min".equalsIgnoreCase(content()))
{
return Optional.of(Byte.MIN_VALUE);
}
else if ("minimum".equalsIgnoreCase(content()))
{
return Optional.of(Byte.MIN_VALUE);
}
else
{
try
{
valueAsByte = Optional.of(Byte.valueOf(content()));
return valueAsByte;
}
catch (RuntimeException ex)
{
return Optional.empty();
}
}
}
/**
* This method retrieves this value, as a short.
*
* @return the value, if possible.
*/
public Optional<Short> asShort ()
{
if (valueAsShort.isPresent())
{
return valueAsShort;
}
else if ("max".equalsIgnoreCase(content()))
{
return Optional.of(Short.MAX_VALUE);
}
else if ("maximum".equalsIgnoreCase(content()))
{
return Optional.of(Short.MAX_VALUE);
}
else if ("min".equalsIgnoreCase(content()))
{
return Optional.of(Short.MIN_VALUE);
}
else if ("minimum".equalsIgnoreCase(content()))
{
return Optional.of(Short.MIN_VALUE);
}
else
{
try
{
valueAsShort = Optional.of(Short.valueOf(content()));
return valueAsShort;
}
catch (RuntimeException ex)
{
return Optional.empty();
}
}
}
/**
* This method retrieves this value, as an integer.
*
* @return the value, if possible.
*/
public Optional<Integer> asInt ()
{
if (valueAsInt.isPresent())
{
return valueAsInt;
}
else if ("max".equalsIgnoreCase(content()))
{
return Optional.of(Integer.MAX_VALUE);
}
else if ("maximum".equalsIgnoreCase(content()))
{
return Optional.of(Integer.MAX_VALUE);
}
else if ("min".equalsIgnoreCase(content()))
{
return Optional.of(Integer.MIN_VALUE);
}
else if ("minimum".equalsIgnoreCase(content()))
{
return Optional.of(Integer.MIN_VALUE);
}
else
{
try
{
valueAsInt = Optional.of(Integer.valueOf(content()));
return valueAsInt;
}
catch (RuntimeException ex)
{
return Optional.empty();
}
}
}
/**
* This method retrieves this value, as a long.
*
* @return the value, if possible.
*/
public Optional<Long> asLong ()
{
if (valueAsLong.isPresent())
{
return valueAsLong;
}
else if ("max".equalsIgnoreCase(content()))
{
return Optional.of(Long.MAX_VALUE);
}
else if ("maximum".equalsIgnoreCase(content()))
{
return Optional.of(Long.MAX_VALUE);
}
else if ("min".equalsIgnoreCase(content()))
{
return Optional.of(Long.MIN_VALUE);
}
else if ("minimum".equalsIgnoreCase(content()))
{
return Optional.of(Long.MIN_VALUE);
}
else
{
try
{
valueAsLong = Optional.of(Long.valueOf(content()));
return valueAsLong;
}
catch (RuntimeException ex)
{
return Optional.empty();
}
}
}
/**
* This method retrieves this value, as a float.
*
* @return the value, if possible.
*/
public Optional<Float> asFloat ()
{
if (valueAsFloat.isPresent())
{
return valueAsFloat;
}
else
{
final Optional<Double> number = asDouble();
valueAsFloat = number.isPresent() ? Optional.of(number.get().floatValue()) : Optional.empty();
return valueAsFloat;
}
}
/**
* This method retrieves this value, as a float.
*
* @return the value, if possible.
*/
public Optional<Double> asDouble ()
{
try
{
if (valueAsDouble.isPresent())
{
return valueAsDouble;
}
else if ("infinity".equalsIgnoreCase(content()))
{
return Optional.of(Double.POSITIVE_INFINITY);
}
else if ("inf".equalsIgnoreCase(content()))
{
return Optional.of(Double.POSITIVE_INFINITY);
}
else if ("-infinity".equalsIgnoreCase(content()))
{
return Optional.of(Double.NEGATIVE_INFINITY);
}
else if ("-inf".equalsIgnoreCase(content()))
{
return Optional.of(Double.NEGATIVE_INFINITY);
}
else if ("nan".equalsIgnoreCase(content()))
{
return Optional.of(Double.NaN);
}
else if ("-nan".equalsIgnoreCase(content()))
{
return Optional.of(-Double.NaN);
}
else
{
valueAsDouble = Optional.of(Double.valueOf(toString()));
return valueAsDouble;
}
}
catch (RuntimeException ex)
{
return Optional.empty();
}
}
/**
* {@inheritDoc}
*/
@Override
public int treeHeight ()
{
return 1;
}
/**
* {@inheritDoc}
*/
@Override
public int treeLeafCount ()
{
return 1;
}
/**
* {@inheritDoc}
*/
@Override
public int treeSize ()
{
return 1;
}
/**
* {@inheritDoc}
*/
@Override
public SourceLocation location ()
{
return location;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode ()
{
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals (final Object obj)
{
if (this == obj)
{
return true;
}
else if (obj == null)
{
return false;
}
else if (obj instanceof SAtom == false)
{
return false;
}
else
{
final SAtom other = (SAtom) obj;
final boolean result = hash == other.hash && toString().equals(other.toString());
return result;
}
}
/**
* {@inheritDoc}
*/
@Override
public final String toString ()
{
return parsableContent;
}
}