AbstractProvider.java

/*
 * #%L
 * prolobjectlink-jpi
 * %%
 * Copyright (C) 2019 Prolobjectlink Project
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */
package io.github.prolobjectlink.prolog;

import java.io.File;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Partial implementation of {@link PrologProvider}
 * 
 * @author Jose Zalacain
 * @since 1.0
 */
public abstract class AbstractProvider implements PrologProvider {

	protected final PrologConverter<?> converter;
	private static final Set<PrologIndicator> ISO_IEC_BUILT_INS;

	static {

		ISO_IEC_BUILT_INS = new HashSet<PrologIndicator>();

		// 7.4 directives
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("dynamic", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("include", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("multifile", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("set_prolog_flag", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("ensure_loaded", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("discontiguous", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("current_prolog_flag", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("initialization", 1));

		// 7.8 control constructs
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("nil", 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("fail", 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("true", 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("false", 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.THROW, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CATCH, 3));

		// 8.2 term unification
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("=", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("\\=", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("subsume", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.UNIFY_WITH_OCCURS_CHECK, 2));

		// 8.3 type testing
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.VAR, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATOM, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.FLOAT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.NUMBER, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.NONVAR, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.OBJECT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.GROUND, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATOMIC, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.INTEGER, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.COMPOUND, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CALLABLE, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CYCLIC_TERM, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ACYCLIC_TERM, 1));

		// 8.4 term comparison
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("@>", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("@<", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("==", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("@>=", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("@=<", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("\\==", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SORT, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.COMPARE, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.KEYSORT, 2));

		// 8.5 term creation and decomposition
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ARG, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.FUNCTOR, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.COPY_TERM, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.TERM_VARIABLES, 2));

		// 8.6 arithmetics evaluation (operator)
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("is", 2));

		// 8.7 arithmetic comparison (operator)
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(">", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("<", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("=<", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(">=", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("=:=", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("=\\=", 2));

		// 8.8 clause retrieval and information ( missing predicate_property/2)
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("clause", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("current_predicate", 2));

		// 8.9 clause creation and destruction
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("abolish", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("asserta", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("assertz", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("retract", 1));

		// 8.10 All solutions
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("forall", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.BAGOF, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SETOF, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.FINDALL, 3));

		// 8.11 Stream Selection and Control
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.OPEN, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CLOSE, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.OPEN, 4));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CLOSE, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SET_INPUT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SET_OUTPUT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CURRENT_INPUT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CURRENT_OUTPUT, 1));

		// 8.12 character input/output
		// 8.13 byte input/output

		// 8.14 Term input/output
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.NL, 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.READ, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.WRITE, 1));

		// 8.15 logic and control
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("call", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("once", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("repeat", 0));

		// 8.16 atomic term processing
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SUB_ATOM, 5));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CHAR_CODE, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATOM_CHARS, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATOM_CODES, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATOM_LENGTH, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATOM_CONCAT, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.NUMBER_CHARS, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.NUMBER_CODES, 2));

		// 8.17 Implementation defined hooks
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.OP, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.HALT, 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.HALT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CURRENT_OP, 3));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CHAR_CONVERSION, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CURRENT_CHAR_CONVERSION, 2));

		// 9.1 simple arithmetic functors
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ABS, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.EXP, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.LOG, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SQRT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CBRT, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.FLOOR, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ROUND, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.CEILING, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.TRUNCATE, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.FLOAT_INTEGER_PART, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.FLOAT_FRACTIONAL_PART, 1));

		// 9.2 ???

		// 9.3 other arithmetic functors
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.MAX, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.MIN, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.GCD, 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.LCM, 2));

		// 9.4 bitwise functors
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("\\//", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("><", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("/\\", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("<<", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(">>", 2));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("\\/", 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator("//", 2));

		// 9.5 trigonometric functors
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SIN, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.COS, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.TAN, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ASIN, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ACOS, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.ATAN, 1));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.SIGN, 1));

		// 9.6 mathematical constants
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.E, 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.PI, 0));
		ISO_IEC_BUILT_INS.add(new DefaultPrologIndicator(PrologBuiltin.EPSILON, 0));

	}

	public AbstractProvider(PrologConverter<?> converter) {
		this.converter = converter;
	}

	protected final String removeQuoted(String functor) {
		if (functor != null && functor.startsWith("\'") && functor.endsWith("\'")) {
			return functor.substring(1, functor.length() - 1);
		}
		return functor;
	}

	public final boolean isCompliant() {
		PrologEngine engine = newEngine();
		Set<PrologIndicator> implemented = engine.getBuiltIns();
		for (PrologIndicator prologIndicator : ISO_IEC_BUILT_INS) {
			if (implemented.contains(prologIndicator)) {
				return true;
			}
		}
		return false;
	}

	public final PrologList parseList(String stringList) {
		PrologTerm term = parseTerm(stringList);
		checkListType(term);
		return (PrologList) term;
	}

	public final PrologClause parseClause(String stringClause) {
		PrologEngine engine = newEngine();
		engine.asserta(stringClause);
		return engine.iterator().next();
	}

	public final PrologStructure parseStructure(String stringStructure) {
		PrologTerm term = parseTerm(stringStructure);
		checkStructureType(term);
		return (PrologStructure) term;
	}

	public final Set<PrologClause> parseProgram(String file) {
		return newEngine(file).getProgramClauses();
	}

	public final Set<PrologClause> parseProgram(File in) {
		return parseProgram(in.getAbsolutePath());
	}

	public final PrologFloat newFloat() {
		return newFloat(0F);
	}

	public final PrologDouble newDouble() {
		return newDouble(0D);
	}

	public final PrologInteger newInteger() {
		return newInteger(0);
	}

	public final PrologLong newLong() {
		return newLong(0L);
	}

	public final PrologList newList(PrologTerm head) {
		return newList(new PrologTerm[] { head });
	}

	public final PrologList newList(Object head) {
		return newList(getJavaConverter().toTerm(head));
	}

	public final PrologList newList(Object[] arguments) {
		return newList(getJavaConverter().toTermsArray(arguments));
	}

	public final PrologList newList(Object head, Object tail) {
		PrologJavaConverter transformer = getJavaConverter();
		PrologTerm headTerm = transformer.toTerm(head);
		PrologTerm tailTerm = transformer.toTerm(tail);
		return newList(headTerm, tailTerm);
	}

	public final PrologList newList(Object[] arguments, Object tail) {
		PrologJavaConverter transformer = getJavaConverter();
		PrologTerm[] array = transformer.toTermsArray(arguments);
		PrologTerm tailTerm = transformer.toTerm(tail);
		return newList(array, tailTerm);
	}

	public final PrologTerm newStructure(String functor, Object... arguments) {
		PrologJavaConverter transformer = getJavaConverter();
		PrologTerm[] parameters = transformer.toTermsArray(arguments);
		return newStructure(functor, parameters);
	}

	public final PrologTerm newStructure(Object left, String operator, Object right) {
		PrologJavaConverter transformer = getJavaConverter();
		PrologTerm leftTerm = transformer.toTerm(left);
		PrologTerm rightTerm = transformer.toTerm(right);
		return newStructure(leftTerm, operator, rightTerm);
	}

	/**
	 * Casts a PrologTerm to the class or interface represented by this
	 * {@code Class} object.
	 *
	 * @param term the object to be cast
	 * @param type the class or interface to be casted
	 * @return the PrologTerm after casting, or null if term is null
	 *
	 * @throws ClassCastException if the object is not null and is not assignable to
	 *                            the type T.
	 * @since 1.1
	 */
	protected final <T extends PrologTerm> T cast(PrologTerm term, Class<T> type) {
		return type.cast(term);
	}

	public final <T extends PrologTerm> T cast(PrologTerm term) {
		return (T) term;
	}

	public final <K extends PrologTerm> K toTerm(Object o, Class<K> from) {
		return converter.toTerm(o, from);
	}

	public final <K extends PrologTerm> K[] toTermArray(Object[] os, Class<K[]> from) {
		return converter.toTermArray(os, from);
	}

	public final <K extends PrologTerm> K[][] toTermMatrix(Object[][] oss, Class<K[][]> from) {
		return converter.toTermMatrix(oss, from);
	}

	public final <K extends PrologTerm, V extends Object> Map<String, PrologTerm> toTermMap(Map<String, V> map,
			Class<K> from) {
		return converter.toTermMap(map, from);
	}

	public final <K extends PrologTerm, V extends Object> Map<String, PrologTerm>[] toTermMapArray(Map<String, V>[] map,
			Class<K> from) {
		return converter.toTermMapArray(map, from);
	}

	public final PrologConverter<?> getConverter() {
		return converter;
	}

	public final <K> K fromTerm(PrologTerm term, Class<K> to) {
		return converter.fromTerm(term, to);
	}

	public final <K> K[] fromTermArray(PrologTerm[] terms, Class<K[]> to) {
		return converter.fromTermArray(terms, to);
	}

	public final <K> K fromTerm(PrologTerm head, PrologTerm[] body, Class<K> to) {
		return converter.fromTerm(head, body, to);
	}

	public final PrologParser getParser() {
		return this;
	}

	public final String getVersion() {
		return newEngine().getVersion();
	}

	public final String getName() {
		return newEngine().getName();
	}

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((converter == null) ? 0 : converter.hashCode());
		return result;
	}

	public boolean equals(Object object) {
		if (this == object)
			return true;
		if (object == null)
			return false;
		if (getClass() != object.getClass())
			return false;
		AbstractProvider other = (AbstractProvider) object;
		if (converter == null) {
			if (other.converter != null)
				return false;
		} else if (!converter.equals(other.converter)) {
			return false;
		}
		return true;
	}

	public abstract String toString();

	private final void checkListType(PrologTerm term) {
		if (!term.isList()) {
			throw new ListExpectedError(term);
		}
	}

	private final void checkStructureType(PrologTerm term) {
		if (!term.isStructure()) {
			throw new StructureExpectedError(term);
		}
	}

}