/*
* Copyright 2014 Igor Maznitsa (http://www.igormaznitsa.com).
*
* 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.igormaznitsa.prol.utils;
import com.igormaznitsa.prol.annotations.Determined;
import com.igormaznitsa.prol.annotations.Predicate;
import com.igormaznitsa.prol.annotations.PredicateSynonyms;
import com.igormaznitsa.prol.annotations.ProlOperator;
import com.igormaznitsa.prol.annotations.ProlOperators;
import com.igormaznitsa.prol.data.*;
import com.igormaznitsa.prol.exceptions.ProlInstantiationErrorException;
import com.igormaznitsa.prol.exceptions.ProlTypeErrorException;
import com.igormaznitsa.prol.libraries.ProlAbstractLibrary;
import com.igormaznitsa.prol.logic.ProlContext;
import com.igormaznitsa.prol.parser.ProlConsult;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
/**
* It is an auxulary class which contains different useful functions for work
* with the Prol engine
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public final class Utils {
/**
* Inside logger, the canonical class name is used as the logger identifier
* (Utils.class.getCanonicalName())
*/
protected static final Logger LOG = Logger.getLogger(Utils.class.getCanonicalName());
/**
* The constant contains a comparator which can compare two terms.
*/
public static final Comparator<Term> TERM_COMPARATOR = new Comparator<Term>() {
@Override
public int compare(final Term term1, final Term term2) {
return term1.termComparsion(term2);
}
};
// hide the constructor
private Utils() {
}
/**
* Get a hash-map filled by variables found in a term, the variables can be
* accessed by their string names.
* <b>NB! Variables are mutable object and the operation doesn't make any copy
* of them, their state will be changed during any next JProl operation, thus
* consider the map as temporary data storage which values you should process
* as soon as possible!</b>
*
* @param term the term which variables should be returned, must not be null
* @return a HashMap object contains pairs name-variables extracted from the term
* @see #fillTableWithVarValues(com.igormaznitsa.prol.data.Term)
*/
public static Map<String, Var> fillTableWithVars(final Term term) {
final Map<String, Var> vars = new HashMap<String, Var>();
term.fillVarables(vars);
return vars;
}
/**
* Extract content of all instantiated non-anonymous variables from the provided term.
* <b>NB! Both Non-instantiated and anonymous variables will be ignored in the result.</b>
* @param term term to be processes, must not be null
* @param map a map to be filled by pairs, it can be null and in the case it will be created
* @return a name-value map for all instantiated variables detected in the provided term
*/
public static Map<String, Term> fillTableWithFoundVarContent(final Term term, final Map<String,Term> map){
final Map<String,Var> vars = fillTableWithVars(term);
final Map<String,Term> result;
if (map == null){
result = new HashMap<String, Term>();
}else{
map.clear();
result = map;
}
for(final Map.Entry<String,Var> e : vars.entrySet()){
final String name = e.getKey();
final Var value = e.getValue();
if (!(value.isAnonymous() || value.isUndefined())){
result.put(name, value.getValue());
}
}
return result;
}
/**
* Return a Number object from a Term if it is possible.
*
* @param term the term which should be converted into a number
* @return a Number object if it is possible
* @throws com.igormaznitsa.prol.exceptions.ProlInstantiationErrorExceptionit
* it will be thrown if the term is a noninstantiated variable.
* @throws com.igormaznitsa.prol.exceptions.ProlTypeErrorException it will be
* thrown if the term is not a numeric term
*/
public static Number getNumberFromElement(Term term) {
if (term.getTermType() == Term.TYPE_VAR) {
final Term origTerm = term;
term = ((Var) term).getValue();
if (term == null) {
throw new ProlInstantiationErrorException("NonInstantiated variable", origTerm);
}
}
if (term instanceof NumericTerm) {
return ((NumericTerm) term).getNumericValue();
}
else {
throw new ProlTypeErrorException("numeric", "NonNumeric term", term);
}
}
/**
* Convert the term into a String. If the term is not instantiated the
* ProlCriticalError will be thrown.
*
* @param term the term to be converted into String.
* @return the term text representation as a String (the same value which will
* be written by write/1 for the term)
* @throws com.igormaznitsa.prol.exceptions.ProlInstantiationErrorExceptionit
* it will be thrown if the term is a non instantiaded variable
*/
public static String getStringFromElement(Term term) {
if (term.getTermType() == Term.TYPE_VAR) {
final Term origTerm = term;
term = ((Var) term).getValue();
if (term == null) {
throw new ProlInstantiationErrorException("NonInstantiated variable", origTerm);
}
}
return term.forWrite();
}
/**
* Find the root throwable object in an excepotion
*
* @param ex a trhowable object to find the root, must not be null
* @return a throwable object which is the root for the cause chain of the
* exception or the same exception if there is not any cause
*/
public static Throwable getRootThrowable(Throwable ex) {
while (true) {
if (ex.getCause() == null) {
break;
}
ex = ex.getCause();
}
return ex;
}
/**
* If the term is a instantiated variable thane the function will return the
* variable value else the same term
*
* @param element the term which shold be processed, must not be null
* @return the value of a variable if the term is an instantiated variable
* else the same term
*/
public static Term getTermFromElement(final Term element) {
if (element.getTermType() == Term.TYPE_VAR) {
final Term val = ((Var) element).getValue();
if (val == null) {
return element;
}
else {
return val;
}
}
else {
return element;
}
}
/**
* Encode a prolog list into a prolog structure where all elements of the list
* will be the elements of the structure. The first element of the list will
* be the functor.
*
* @param context a prol context which will be used during the operation to
* find predicate processors, must not be null.
* @param list the list to be converted into a prolog structure, must not be
* null
* @return if the list is the null list, the <empty> term will be returned,
* else a prolog structure contains the first list element as the functor and
* other list elements as elements of the structure.
*/
public static Term getListAsAtom(final ProlContext context, final TermList list) {
if (list == TermList.NULLLIST) {
return new Term("<empty>");
}
if (list.getTail() == TermList.NULLLIST) {
return list.getHead();
}
else {
final int length = list.calculateLength();
if (length == 3) {
// may be it is a list
if (list.getHead().getText().equals(".")) {
final TermList secondElement = (TermList) list.getTail();
final TermList thirdElement = (TermList) secondElement.getTail();
if (thirdElement.getHead().getTermType() == Term.TYPE_LIST) {
// it's a list
return new TermList(secondElement.getHead(), thirdElement.getHead());
}
}
}
final TermStruct result;
if (length == 1) {
result = new TermStruct(list.getHead());
}
else {
Term[] elements = new Term[length - 1];
TermList lst = (TermList) list.getTail();
int index = 0;
while (lst != TermList.NULLLIST) {
elements[index++] = lst.getHead();
lst = (TermList) lst.getTail();
}
result = new TermStruct(list.getHead(), elements);
}
result.setPredicateProcessor(context.findProcessor(result));
return result;
}
}
/**
* Convert a term array into a term list
*
* @param array an array to be converted
* @return if an array is null or has zero length then NULLLIST will be
* returned else a TermList contains all elements of the array
*/
public static TermList arrayToList(final Term[] array) {
if (array == null || array.length == 0) {
return TermList.NULLLIST;
}
final TermList result = new TermList(array[0]);
TermList next = result;
final int length = array.length;
for (int li = 1; li < length; li++) {
next = TermList.appendItem(next, array[li]);
}
return result;
}
/**
* Convert a term list into a term array
*
* @param list the list to be converted into an array, must not be null
* @return a term array contains all elements from the list, if the list is a
* NULLLIST then zero length array will be returned
*/
public static Term[] listToArray(final TermList list) {
if (list.isNullList()) {
return new Term[0];
}
final ArrayList<Term> arraylist = new ArrayList<Term>();
TermList curlist = list;
while (true) {
if (curlist.isNullList()) {
break;
}
arraylist.add(curlist.getHead());
final Term nextList = curlist.getTail();
if (nextList.getTermType() == Term.TYPE_LIST) {
curlist = (TermList) nextList;
}
else {
arraylist.add(nextList);
break;
}
}
return arraylist.toArray(new Term[arraylist.size()]);
}
/**
* Unroll a term into a term list.
*
* @param element a term to be unrolled into a term list, must not be null
* @return a term list contains elements form the source term
*/
public static TermList unrollTermIntoList(final Term element) {
switch (element.getTermType()) {
case Term.TYPE_LIST:
return (TermList) element;
case Term.TYPE_STRUCT: {
final TermStruct struct = (TermStruct) element;
final TermList result = new TermList(struct.getFunctor());
TermList curResult = result;
final int arity = struct.getArity();
for (int li = 0; li < arity; li++) {
curResult = TermList.appendItem(curResult, struct.getElement(li));
}
return result;
}
default:
return new TermList(element);
}
}
/**
* Arrange variables (replace a variable for a name by the such named value
* from the mapping table) in two terms
*
* @param struct the term to be processed, must not be null
* @param variables the table used to save mapped variables, must not be null
*/
private static void processTermForArrangeVariables(final Term term, final Map<String, Var> variables) {
switch (term.getTermType()) {
case Term.TYPE_LIST: {
TermList list = (TermList) term;
while (!list.isNullList()) {
processTermForArrangeVariables(list.getHead(), variables);
final Term tail = list.getTail();
if (tail.getTermType() == Term.TYPE_LIST) {
list = (TermList) tail;
}
else {
processTermForArrangeVariables(tail, variables);
break;
}
}
}
break;
case Term.TYPE_STRUCT: {
final TermStruct struct = (TermStruct) term;
final int arity = struct.getArity();
for (int li = 0; li < arity; li++) {
final Term element = struct.getElement(li);
if (element.getTermType() == Term.TYPE_VAR) {
final String varname = element.getText();
if (((Var) element).isUndefined()) {
final Var var = variables.get(varname);
if (var == null) {
variables.put(varname, (Var) element);
}
else {
struct.setElement(li, var);
}
}
}
else {
processTermForArrangeVariables(element, variables);
}
}
}
break;
case Term.TYPE_VAR: {
final String name = ((Var) term).getText();
if (variables.containsKey(name)) {
final Var var = variables.get(name);
term.Equ(var);
}
else {
variables.put(name, (Var) term);
}
}
break;
}
}
/**
* Arrange variables between two terms (to make named variable links to the
* same objects in both terms)
*
* @param termOne the first term, must not be null
* @param termTwo the second term, must not be null
*/
public static void arrangeVariablesInsideTerms(final Term termOne, final Term termTwo) {
final Map<String, Var> varMap = new HashMap<String, Var>();
processTermForArrangeVariables(termOne, varMap);
processTermForArrangeVariables(termTwo, varMap);
}
/**
* Out all information about found predicates in a prol library into a
* PrintOut
*
* @param out a PrintStream to output the text information, must not be null
* @param libraryClass a library class which will be processed to find all
* predicate definitions inside, must not be null
* @see com.igormaznitsa.prol.libraries.ProlAbstractLibrary
*/
@SuppressWarnings("unchecked")
public static void printPredicatesForLibrary(final PrintStream out, final Class<?> libraryClass) {
if (!ProlAbstractLibrary.class.isAssignableFrom(libraryClass)) {
out.println(libraryClass.getCanonicalName() + " is not an AbstractLibrary class");
return;
}
final Method[] methods = libraryClass.getMethods();
out.println(libraryClass.getCanonicalName());
out.println("===============================================");
final ProlOperators operators = libraryClass.getAnnotation(ProlOperators.class);
if (operators != null) {
// there is defined operators
final ProlOperator[] ops = operators.Operators();
if (ops.length > 0) {
out.println("Operators\n-----------------------");
for (int li = 0; li < ops.length; li++) {
final ProlOperator oper = ops[li];
if (oper.Priority() > 0) {
out.println(":-op(" + oper.Priority() + "," + Operator.getTypeFromIndex(oper.Type()) + ",\'" + oper.Name() + "\').");
}
}
out.println("-----------------------");
}
}
for (int li = 0; li < methods.length; li++) {
final Method method = methods[li];
final Predicate predicate = method.getAnnotation(Predicate.class);
if (predicate != null) {
final boolean determined = method.getAnnotation(Determined.class) != null;
final PredicateSynonyms predicateSynonims = method.getAnnotation(PredicateSynonyms.class);
out.print(predicate.Signature());
if (predicateSynonims != null) {
out.print(" {");
final String[] signatures = predicateSynonims.Signatures();
for (int ls = 0; ls < signatures.length; ls++) {
if (ls > 0) {
out.print(", ");
}
out.print(signatures[ls]);
}
out.print("}");
}
if (determined) {
out.print(" [DETERMINED]");
}
out.println();
final String[] templates = predicate.Template();
for (int lt = 0; lt < templates.length; lt++) {
out.println('[' + templates[lt] + ']');
}
final String reference = predicate.Reference();
if (reference != null && reference.length() > 0) {
out.println();
out.println(reference);
}
out.println("---------------------\r\n");
}
}
}
/**
* Out a number spaces into a print stream
*
* @param out the print stream for the output, must not be null
* @param number the number of space chars to be out into the stream
*/
public static void spaces(final PrintStream out, final int number) {
for (int li = 0; li < number; li++) {
out.print(' ');
}
}
/**
* Out a term as a tree into the System.out
*
* @param term the term to be shown as a tree, must not be null
*/
public static void printTree(final Term term) {
printTree(System.out, 0, term);
}
/**
* Out a term as a tree into a PrintStream
*
* @param out the output stream which will be used for the output, must not be
* null
* @param spaces the output position offset from the left side
* @param term the term to be out, must not be null
*/
public static void printTree(final PrintStream out, final int spaces, final Term term) {
switch (term.getTermType()) {
case Term.TYPE_STRUCT: {
final TermStruct struct = (TermStruct) term;
if (struct.getFunctor().getTermType() == Term.TYPE_OPERATOR) {
out.println('(' + struct.getFunctor().toString() + ')');
final int spaces2 = spaces + 1;
spaces(out, spaces2);
out.println('|');
for (int li = 0; li < struct.getArity(); li++) {
spaces(out, spaces2);
out.print("\\-");
printTree(out, spaces2 + 2, struct.getElement(li));
}
}
else {
out.println(term.toString());
}
}
break;
default: {
out.println(term);
}
}
}
/**
* Out a term state into the System.out
*
* @param term the term which state will be out, must not be null
*/
public static void printTermState(final Term term) {
printTermState(System.out, term);
}
/**
* Out a term state into a PrintStream
*
* @param out a PrintStream to be used for output, must not be null
* @param term a term to be out into the stream
*/
public static void printTermState(final PrintStream out, final Term term) {
out.println(term.getSourceLikeRepresentation());
final Map<String, Var> vars = Utils.fillTableWithVars(term);
final Iterator<Var> iter = vars.values().iterator();
while (iter.hasNext()) {
final Var variable = iter.next();
if (variable.isAnonymous()) {
continue;
}
out.print(variable.getText());
out.print("{uid=" + variable.getVarUID() + '}');
out.print('=');
if (variable.isUndefined()) {
out.println("???");
}
else {
out.println(variable.toString());
}
}
}
/**
* Encode text string to be possible use it in a prolog source file
*
* @param string a string to be processed, must not be null
* @return the result of the processing as a String
*/
public static String encodeTextSourceLike(final String string) {
final String text = string;
if (text.length() == 0) {
return text;
}
final StringBuilder builder = new StringBuilder(string.length());
for (int li = 0; li < text.length(); li++) {
final char curChar = text.charAt(li);
switch (curChar) {
case '\\':
builder.append("\\\\");
break;
case '\'':
builder.append("\\\'");
break;
case '\"':
builder.append("\\\"");
break;
case '\n':
builder.append("\\n");
break;
case '\f':
builder.append("\\f");
break;
case '\r':
builder.append("\\r");
break;
case '\t':
builder.append("\\t");
break;
case '_': {
builder.append('_');
}
break;
case '%':
case '.': {
builder.append(curChar);
}
break;
default: {
builder.append(curChar);
}
}
}
return builder.toString();
}
/**
* The function allows to make a consultation from a resource addresed by its
* URL. You can use "this://[resource_path]" to get access to the resource
* through ContextClassLoader.getResourceAsStrean()
*
* @param url the url of a resource which will be used for the consultation,
* must not be null
* @param context the context which will be as the owner for the information,
* must not be null
* @throws IOException it will be thrown if the information can't be read from
* the source
* @throws InterruptedException it will be thrown if the thread has been
* interrupted
*/
public static void consultFromURLConnection(final String url, final ProlContext context) throws IOException, InterruptedException {
if (url == null || context == null) {
throw new IllegalArgumentException("There is a null as an argument");
}
final InputStream inStream;
if (url.startsWith("this://")) {
final String purePath = url.substring(7); // remove the prefix
inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(purePath);
if (inStream == null) {
throw new IOException("Can't find the resource addressed as \"" + purePath + "\" through the class loader");
}
}
else {
final URL parsed = new URL(url);
final URLConnection connection = parsed.openConnection();
connection.setDoInput(true);
connection.setDoOutput(false);
connection.setAllowUserInteraction(false);
connection.setUseCaches(true);
inStream = connection.getInputStream();
}
try {
new ProlConsult(inStream, context).consult();
}
finally {
if (inStream != null) {
try {
inStream.close();
}
catch (Exception thr) {
LOG.log(Level.WARNING, "consultFromURLConnection().close", thr);
}
}
}
}
/**
* Allows to decode a structure contains a term signature and usually
* represented as predicate_name/arity (as an example test/2)
*
* @param term a predicate which is either a structure or a variable
* instantiated by a structure, must not be null
* @return decoded predicate signature as a String or null if it is impossible
* to extract a signature from the structure
*/
public static String extractPredicateSignatureFromStructure(final Term term) {
final TermStruct struct = (TermStruct) Utils.getTermFromElement(term);
if (struct.getArity() != 2) {
return null;
}
final Term left = getTermFromElement(struct.getElement(0));
final Term right = getTermFromElement(struct.getElement(1));
if (right instanceof TermInteger && left.getTermType() == Term.TYPE_ATOM) {
return left.getText() + '/' + right.getText();
}
return null;
}
/**
* Check that the signature has normal view (name/arity) and if not
* ('name'/arity), then correct it
*
* @param signature a signature to be checked and corrected, must not be null
* @return corrected signature as a String or null if the argument is not a
* signature
*/
public static String normalizeSignature(final String signature) {
if (signature == null) {
return null;
}
String sig = signature.trim();
if (sig.length() > 0 && sig.charAt(0) == '\'') {
sig = sig.substring(1);
final int lastIndex = sig.lastIndexOf('/');
if (lastIndex < 0) {
sig = null;
}
else {
final String arity = sig.substring(lastIndex + 1).trim();
String name = sig.substring(0, lastIndex - 1).trim();
if (name.length() > 0 && name.charAt(name.length() - 1) == '\'') {
name = name.substring(0, name.length() - 1);
sig = name + '/' + arity;
}
else {
sig = null;
}
}
}
return sig;
}
/**
* Allows to get a Var contained inside a Term
*
* @param term the term which will be used for search, must not be null
* @param name the variable name, must not be null
* @return a Var object for the name or null if it is not found
*/
public static Var findVarInsideTerm(final Term term, final String name) {
if (term == null || name == null) {
throw new NullPointerException();
}
Var result = null;
switch (term.getTermType()) {
case Term.TYPE_STRUCT: {
final TermStruct struct = (TermStruct) term;
final Term[] elements = struct.getElementsAsArray();
for (int li = 0; li < elements.length; li++) {
result = findVarInsideTerm(elements[li], name);
if (result != null) {
break;
}
}
}
break;
case Term.TYPE_LIST: {
final TermList list = (TermList) term;
if (!list.isNullList()) {
final Term headterm = list.getHead();
if (headterm != null) {
result = findVarInsideTerm(headterm, name);
}
if (result == null) {
final Term tailterm = list.getTail();
if (tailterm != null) {
result = findVarInsideTerm(tailterm, name);
}
}
}
}
break;
case Term.TYPE_VAR: {
final Var varTerm = (Var) term;
if (varTerm.getText().equals(name)) {
result = varTerm;
}
else {
final Term value = varTerm.getThisValue();
if (value != null) {
result = findVarInsideTerm(value, name);
}
}
}
break;
default: {
}
break;
}
return result;
}
public static String validateSignature(final String signature) {
if (signature == null) {
throw new NullPointerException("Null signature detected");
}
final String[] parsed = signature.split("/");
if (parsed.length == 2) {
String str = parsed[0].trim();
boolean quoted = false;
if (str.length() != 0) {
if (str.charAt(0) == '\'') {
if (str.length() > 1 && str.charAt(str.length() - 1) == '\'') {
str = str.substring(1, str.length() - 1);
if (str.length() == 0) {
return null;
}
quoted = true;
}
else {
// wrong name, it must not contain '\'' as the only symbol
return null;
}
}
final char firstChar = str.charAt(0);
if (!quoted && (Character.isDigit(firstChar) || Character.isUpperCase(firstChar) || Character.isWhitespace(firstChar) || firstChar == '.')) {
return null;
}
// ok. the first part is ok, check the second part
final int arity;
try {
arity = Integer.parseInt(parsed[1].trim());
if (arity < 0) {
throw new NumberFormatException("Negate number is not supported as arity");
}
}
catch (NumberFormatException ex) {
return null;
}
final StringBuilder builder = new StringBuilder(signature.length());
if (quoted) {
builder.append('\'').append(str).append('\'').append('/').append(arity);
}
else {
builder.append(str).append('/').append(arity);
}
return builder.toString();
}
}
return null;
}
public static void assertSwingThread() {
if (!SwingUtilities.isEventDispatchThread()) {
throw new Error("Must e called in Swing Dispatch Event Thread");
}
}
}