AbstractClause.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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * Partial implementation of {@link PrologClause} interface.
 * 
 * @author Jose Zalacain
 * @since 1.0
 */
public abstract class AbstractClause implements PrologClause {

	private String see = "";
	private String since = "";
	private String author = "";
	private String version = "";
	private String description = "";

	private boolean dynamic;
	private boolean multifile;
	private boolean discontiguous;

	private final PrologTerm head;
	private final PrologTerm body;

	protected final PrologProvider provider;

	/**
	 * Create a new fact clause. A fatc clause is only represented by clause head
	 * and no have clause body. The body for this clause type is null. The other
	 * parameters are boolean clause properties. If a clause have any of this
	 * properties specify with true value.
	 * 
	 * @param provider      prolog provider
	 * @param head          clause head
	 * @param dynamic       true if clause is dynamic, false otherwise
	 * @param multifile     true if clause is multifile, false otherwise
	 * @param discontiguous true if clause is discontiguous, false otherwise
	 * @since 1.0
	 */
	protected AbstractClause(PrologProvider provider, PrologTerm head, boolean dynamic, boolean multifile,
			boolean discontiguous) {
		this(provider, head, null, dynamic, multifile, discontiguous);
	}

	/**
	 * Create a new rule clause. A rule clause is represented by clause head and
	 * body. The other parameters are boolean clause properties. If a clause have
	 * any of this properties specify with true value.
	 * 
	 * @param provider      prolog provider
	 * @param head          clause head
	 * @param body          clause body
	 * @param dynamic       true if clause is dynamic, false otherwise
	 * @param multifile     true if clause is multifile, false otherwise
	 * @param discontiguous true if clause is discontiguous, false otherwise
	 * @since 1.0
	 */
	protected AbstractClause(PrologProvider provider, PrologTerm head, PrologTerm body, boolean dynamic,
			boolean multifile, boolean discontiguous) {
		this.head = head;
		this.body = body;
		this.provider = provider;
		this.dynamic = dynamic;
		this.multifile = multifile;
		this.discontiguous = discontiguous;
	}

	public PrologTerm getTerm() {
		PrologTerm h = getHead();
		if (isRule()) {
			PrologTerm b = getBody();
			return provider.newStructure(":-", h, b);
		}
		return h;
	}

	public final PrologTerm getHead() {
		return head;
	}

	public final PrologTerm getBody() {
		return body;
	}

	public final PrologTerm[] getBodyArray() {
		PrologTerm ptr = getBody();
		List<PrologTerm> terms = new ArrayList<PrologTerm>();
		while (ptr != null && ptr.isCompound() && ptr.hasIndicator(",", 2)) {
			terms.add(ptr.getArgument(0));
			ptr = ptr.getArgument(1);
		}
		terms.add(ptr);
		return terms.toArray(new PrologTerm[0]);
	}

	public final Iterator<PrologTerm> getBodyIterator() {
		return new BodyIterator(getBodyArray());
	}

	public final String getFunctor() {
		return head.getFunctor();
	}

	public final int getArity() {
		return head.getArity();
	}

	public PrologTerm[] getArguments() {
		return head.getArguments();
	}

	public PrologTerm getArgument(int index) {
		return head.getArgument(index);
	}

	public final boolean hasIndicator(String functor, int arity) {
		return getHead().hasIndicator(functor, arity);
	}

	public final String getIndicator() {
		return head.getIndicator();
	}

	public final boolean isDirective() {
		return head == null && body != null;
	}

	public final boolean isFact() {
		return head != null && body == null;
	}

	public final boolean isRule() {
		return head != null && body != null;
	}

	public boolean isMethod() {
		return false;
	}

	public boolean isFunction() {
		return false;
	}

	public final boolean isClause() {
		return true;
	}

	public final boolean isTerm() {
		return false;
	}

	public final boolean unify(PrologClause clause) {
		return head.unify(clause.getHead()) && body.unify(clause.getBody());
	}

	public final boolean isDynamic() {
		return dynamic;
	}

	public final boolean isMultifile() {
		return multifile;
	}

	public final boolean isDiscontiguous() {
		return discontiguous;
	}

	public PrologIndicator getPrologIndicator() {
		return new DefaultPrologIndicator(getFunctor(), getArity());
	}

	public final <T extends PrologClause> T cast() {
		return (T) this;
	}

	public final String getSee() {
		return see;
	}

	public final void setSee(String see) {
		this.see = see;
	}

	public final String getSince() {
		return since;
	}

	public final void setSince(String since) {
		this.since = since;
	}

	public final String getAuthor() {
		return author;
	}

	public final void setAuthor(String author) {
		this.author = author;
	}

	public final String getVersion() {
		return version;
	}

	public final void setVersion(String version) {
		this.version = version;
	}

	public final String getDescription() {
		return description;
	}

	public final void setDescription(String description) {
		this.description = description;
	}

	public final boolean hasDescription() {
		return !getDescription().isEmpty();
	}

	public final boolean hasVersion() {
		return !getVersion().isEmpty();
	}

	public final boolean hasAuthor() {
		return !getAuthor().isEmpty();
	}

	public final boolean hasSince() {
		return !getSince().isEmpty();
	}

	public final boolean hasSee() {
		return !getSee().isEmpty();
	}

	public final boolean hasDocumentation() {
		return hasDescription() || hasVersion() || hasAuthor() || hasSince() || hasSee();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((body == null) ? 0 : body.hashCode());
		result = prime * result + ((description == null) ? 0 : description.hashCode());
		result = prime * result + (discontiguous ? 1231 : 1237);
		result = prime * result + (dynamic ? 1231 : 1237);
		result = prime * result + ((head == null) ? 0 : head.hashCode());
		result = prime * result + (multifile ? 1231 : 1237);
		result = prime * result + ((see == null) ? 0 : see.hashCode());
		result = prime * result + ((since == null) ? 0 : since.hashCode());
		result = prime * result + ((version == null) ? 0 : version.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		AbstractClause other = (AbstractClause) obj;
		if (author == null) {
			if (other.author != null)
				return false;
		} else if (!author.equals(other.author))
			return false;
		if (body == null) {
			if (other.body != null)
				return false;
		} else if (!body.equals(other.body))
			return false;
		if (description == null) {
			if (other.description != null)
				return false;
		} else if (!description.equals(other.description))
			return false;
		if (discontiguous != other.discontiguous)
			return false;
		if (dynamic != other.dynamic)
			return false;
		if (head == null) {
			if (other.head != null)
				return false;
		} else if (!head.equals(other.head))
			return false;
		if (multifile != other.multifile)
			return false;
		if (see == null) {
			if (other.see != null)
				return false;
		} else if (!see.equals(other.see))
			return false;
		if (since == null) {
			if (other.since != null)
				return false;
		} else if (!since.equals(other.since))
			return false;
		if (version == null) {
			if (other.version != null)
				return false;
		} else if (!version.equals(other.version))
			return false;
		return true;
	}

	@Override
	public String toString() {
		StringBuilder b = new StringBuilder();
		if (hasDocumentation()) {
			b.append("/*");
			if (hasDescription()) {
				b.append(getDescription());
				b.append("\n");
			}
			if (hasVersion()) {
				b.append(getVersion());
				b.append("\n");
			}
			if (hasAuthor()) {
				b.append(getAuthor());
				b.append("\n");
			}
			if (hasSince()) {
				b.append(getSince());
				b.append("\n");
			}
			if (hasSee()) {
				b.append(getSee());
				b.append("\n");
			}
			b.append("*/");
		}
		b.append(getHead());
		if (isRule()) {
			b.append(":-\n\t");
			Iterator<PrologTerm> i = getBodyIterator();
			while (i.hasNext()) {
				b.append(i.next());
				if (i.hasNext()) {
					b.append(",\n\t");
				}
			}
		}
		b.append('.');
		return "" + b + "";
	}

	private class BodyIterator extends AbstractIterator<PrologTerm> implements Iterator<PrologTerm> {

		private int nextIndex;

		private final PrologTerm[] elements;

		protected BodyIterator(PrologTerm[] elements) {
			this.elements = elements;
		}

		public boolean hasNext() {
			return nextIndex < elements.length;
		}

		public PrologTerm next() {
			if (!hasNext()) {
				throw new NoSuchElementException();
			}
			return elements[nextIndex++];
		}

	}

}