View Javadoc

1   /*
2    * #%L
3    * prolobjectlink-jpi
4    * %%
5    * Copyright (C) 2019 Prolobjectlink Project
6    * %%
7    * Permission is hereby granted, free of charge, to any person obtaining a copy
8    * of this software and associated documentation files (the "Software"), to deal
9    * in the Software without restriction, including without limitation the rights
10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11   * copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   * 
14   * The above copyright notice and this permission notice shall be included in
15   * all copies or substantial portions of the Software.
16   * 
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23   * THE SOFTWARE.
24   * #L%
25   */
26  package io.github.prolobjectlink.prolog;
27  
28  import static io.github.prolobjectlink.prolog.PrologTermType.ATOM_TYPE;
29  import static io.github.prolobjectlink.prolog.PrologTermType.CLASS_TYPE;
30  import static io.github.prolobjectlink.prolog.PrologTermType.CUT_TYPE;
31  import static io.github.prolobjectlink.prolog.PrologTermType.DOUBLE_TYPE;
32  import static io.github.prolobjectlink.prolog.PrologTermType.FAIL_TYPE;
33  import static io.github.prolobjectlink.prolog.PrologTermType.FALSE_TYPE;
34  import static io.github.prolobjectlink.prolog.PrologTermType.FIELD_TYPE;
35  import static io.github.prolobjectlink.prolog.PrologTermType.FLOAT_TYPE;
36  import static io.github.prolobjectlink.prolog.PrologTermType.INTEGER_TYPE;
37  import static io.github.prolobjectlink.prolog.PrologTermType.LIST_TYPE;
38  import static io.github.prolobjectlink.prolog.PrologTermType.LONG_TYPE;
39  import static io.github.prolobjectlink.prolog.PrologTermType.MAP_ENTRY_TYPE;
40  import static io.github.prolobjectlink.prolog.PrologTermType.MAP_TYPE;
41  import static io.github.prolobjectlink.prolog.PrologTermType.MIXIN_TYPE;
42  import static io.github.prolobjectlink.prolog.PrologTermType.NIL_TYPE;
43  import static io.github.prolobjectlink.prolog.PrologTermType.OBJECT_TYPE;
44  import static io.github.prolobjectlink.prolog.PrologTermType.PARAMETER_TYPE;
45  import static io.github.prolobjectlink.prolog.PrologTermType.RESULT_TYPE;
46  import static io.github.prolobjectlink.prolog.PrologTermType.STRUCTURE_TYPE;
47  import static io.github.prolobjectlink.prolog.PrologTermType.TRUE_TYPE;
48  import static io.github.prolobjectlink.prolog.PrologTermType.VARIABLE_TYPE;
49  
50  import java.util.ArrayDeque;
51  import java.util.Deque;
52  import java.util.HashMap;
53  import java.util.Map;
54  
55  /**
56   * Partial implementation of {@link PrologTerm} interface.
57   * 
58   * @author Jose Zalacain
59   * @since 1.0
60   */
61  public abstract class AbstractTerm implements PrologTerm {
62  
63  	protected int type;
64  	protected final PrologProvider provider;
65  
66  	protected AbstractTerm(int type, PrologProvider provider) {
67  		this.type = type;
68  		this.provider = provider;
69  	}
70  
71  	protected final void checkIndex(int index) {
72  		if (index < 0) {
73  			throw new ArrayIndexOutOfBoundsException(index);
74  		}
75  	}
76  
77  	protected final void checkIndex(int index, int max) {
78  		if (index < 0 || index > max) {
79  			throw new ArrayIndexOutOfBoundsException(index);
80  		}
81  	}
82  
83  	protected final String removeQuoted(String functor) {
84  		if (functor != null && functor.startsWith("\'") && functor.endsWith("\'")) {
85  			return functor.substring(1, functor.length() - 1);
86  		}
87  		return functor;
88  	}
89  
90  	protected final <K extends PrologTerm> K toTerm(Object o, Class<K> from) {
91  		return provider.toTerm(o, from);
92  	}
93  
94  	protected final <K extends PrologTerm> K[] toTermArray(Object[] os, Class<K[]> from) {
95  		return provider.toTermArray(os, from);
96  	}
97  
98  	protected final <K> K fromTerm(PrologTerm term, Class<K> to) {
99  		return provider.fromTerm(term, to);
100 	}
101 
102 	protected final <K> K[] fromTermArray(PrologTerm[] terms, Class<K[]> to) {
103 		return provider.fromTermArray(terms, to);
104 	}
105 
106 	protected final PrologLogger getLogger() {
107 		return provider.getLogger();
108 	}
109 
110 	public PrologTerm getArgument(int index) {
111 		PrologTerm[] array = getArguments();
112 		checkIndex(index, array.length);
113 		return array[index];
114 	}
115 
116 	public final String getIndicator() {
117 		return getFunctor() + "/" + getArity();
118 	}
119 
120 	public final boolean hasIndicator(String functor, int arity) {
121 		return getFunctor().equals(functor) && getArity() == arity;
122 	}
123 
124 	public PrologTerm getTerm() {
125 		return this;
126 	}
127 
128 	public final int getType() {
129 		return type;
130 	}
131 
132 	public final PrologProvider getProvider() {
133 		return provider;
134 	}
135 
136 	/**
137 	 * Casts a PrologTerm to the class or interface represented by this
138 	 * {@code Class} object.
139 	 *
140 	 * @param term the object to be cast
141 	 * @param type the class or interface to be casted
142 	 * @return the PrologTerm after casting, or null if term is null
143 	 *
144 	 * @throws ClassCastException if the object is not null and is not assignable to
145 	 *                            the type T.
146 	 * @since 1.1
147 	 */
148 	protected final <T extends PrologTerm> T cast(PrologTerm term, Class<T> type) {
149 		return type.cast(term);
150 	}
151 
152 	/**
153 	 * Casts a PrologTerm to the class or interface represented by this
154 	 * {@code Class} object.
155 	 *
156 	 * @param term the object to be cast
157 	 * @return the PrologTerm after casting, or null if term is null
158 	 *
159 	 * @throws ClassCastException if the object is not null and is not assignable to
160 	 *                            the type T.
161 	 * @since 1.1
162 	 */
163 	protected final <T extends PrologTerm> T cast(PrologTerm term) {
164 		return (T) term;
165 	}
166 
167 	public final <T extends PrologTerm> T cast() {
168 		return cast(this);
169 	}
170 
171 	public Object getObject() {
172 		PrologTerm term = this;
173 		switch (term.getType()) {
174 		case NIL_TYPE:
175 			return null;
176 		case CUT_TYPE:
177 			return "!";
178 		case FAIL_TYPE:
179 			return "fail";
180 		case TRUE_TYPE:
181 			return true;
182 		case FALSE_TYPE:
183 			return false;
184 		case ATOM_TYPE:
185 			return term.getFunctor();
186 		case FLOAT_TYPE:
187 			return ((PrologNumber) term).getFloatValue();
188 		case INTEGER_TYPE:
189 			return ((PrologNumber) term).getIntegerValue();
190 		case DOUBLE_TYPE:
191 			return ((PrologNumber) term).getDoubleValue();
192 		case LONG_TYPE:
193 			return ((PrologNumber) term).getLongValue();
194 		case VARIABLE_TYPE:
195 			return ((PrologVariable) term).getName();
196 		case LIST_TYPE:
197 			return fromTermArray(term.getArguments(), Object[].class);
198 		case STRUCTURE_TYPE:
199 			return term;
200 		case OBJECT_TYPE:
201 			return term.getObject();
202 		case FIELD_TYPE:
203 			PrologVariable field = term.cast();
204 			return "field " + field.getName();
205 		case RESULT_TYPE:
206 			PrologVariable result = term.cast();
207 			return "result " + result.getName();
208 		case PARAMETER_TYPE:
209 			PrologVariable parameter = term.cast();
210 			return "parameter " + parameter.getName();
211 		case CLASS_TYPE:
212 		case MIXIN_TYPE:
213 			return "class " + term.getFunctor();
214 		default:
215 			return null;
216 		}
217 	}
218 
219 	public final boolean isEntry() {
220 		return getType() == MAP_ENTRY_TYPE;
221 	}
222 
223 	public final boolean isMap() {
224 		return getType() == MAP_TYPE;
225 	}
226 
227 	public boolean isField() {
228 		return getType() == FIELD_TYPE;
229 	}
230 
231 	public boolean isResult() {
232 		return getType() == RESULT_TYPE;
233 	}
234 
235 	public boolean isParameter() {
236 		return getType() == PARAMETER_TYPE;
237 	}
238 
239 	public final boolean isMixin() {
240 		return getType() == MIXIN_TYPE;
241 	}
242 
243 	public final boolean isClass() {
244 		return getType() == CLASS_TYPE;
245 	}
246 
247 	public boolean isVariableBound() {
248 		return isVariable() && getTerm() != this;
249 	}
250 
251 	public boolean isVariableNotBound() {
252 		return isVariable() && getTerm() == this;
253 	}
254 
255 	public final boolean isClause() {
256 		return false;
257 	}
258 
259 	public final boolean isTerm() {
260 		return true;
261 	}
262 
263 	/**
264 	 * Match to other term returning list of substitutions.
265 	 * 
266 	 * @param term - term to match check
267 	 * @return list of substitutions.
268 	 */
269 	public final Map<String, PrologTerm> match(PrologTerm term) {
270 		Deque<PrologTerm> stack = new ArrayDeque<PrologTerm>();
271 		if (unify(term, stack)) {
272 			int size = stack.size();
273 			HashMap<String, PrologTerm> substitution = new HashMap<String, PrologTerm>(size);
274 			while (size > 0) {
275 				PrologVariable variable = (PrologVariable) stack.pop();
276 				substitution.put(variable.getName(), variable.getTerm());
277 				// variable.unbind();
278 				size--;
279 			}
280 			return substitution;
281 		}
282 		return new HashMap<String, PrologTerm>();
283 	}
284 
285 	/**
286 	 * Unification is the basic primitive operation in logic programming. Check that
287 	 * two terms (x and y) unify. Prolog unification algorithm is based on three
288 	 * principals rules:
289 	 * <ul>
290 	 * <li>If x and y are atomics constants then x and y unify only if they are same
291 	 * object.</li>
292 	 * <li>If x is a variable and y is anything then they unify and x is
293 	 * instantiated to y. Conversely, if y is a variable then this is instantiated
294 	 * to x.</li>
295 	 * <li>If x and y are structured terms then unify only if they match (equals
296 	 * funtor and arity) and all their correspondents arguments unify.</li>
297 	 * </ul>
298 	 * 
299 	 * 
300 	 * @param term  - the term to unify with the current term
301 	 * @param stack - the stack is used to store the addresses of variables which
302 	 *              are bound by the unification. This is needed when backtracking.
303 	 * @return true if the specified term unify whit the current term, false if not
304 	 */
305 	private boolean unify(PrologTerm term, Deque<PrologTerm> stack) throws PrologError {
306 
307 		PrologTerm thisTerm = this;
308 		PrologTerm otherTerm = term;
309 
310 		if (thisTerm.isVariableBound()) {
311 			return ((AbstractTerm) thisTerm.getTerm()).unify(otherTerm, stack);
312 		}
313 
314 		else if (otherTerm.isVariableBound()) {
315 			return ((AbstractTerm) otherTerm.getTerm()).unify(thisTerm, stack);
316 		}
317 
318 		// current term is a free variable
319 		else if (thisTerm.isVariableNotBound()) {
320 			// if (!thisTerm.occurs(otherTerm)) {
321 			// thisTerm.bind(otherTerm);
322 			stack.push(thisTerm);
323 			return true;
324 			// }
325 		}
326 
327 		// the other term is a free variable
328 		else if (otherTerm.isVariableNotBound()) {
329 			// if (!otherTerm.occurs(thisTerm)) {
330 			// otherTerm.bind(thisTerm);
331 			stack.push(otherTerm);
332 			return true;
333 			// }
334 		}
335 
336 		// if at least term is a number then check equivalence
337 		else if (thisTerm.isNumber() || otherTerm.isNumber()) {
338 			if ((thisTerm.isInteger() || thisTerm.isLong()) && (otherTerm.isInteger() || otherTerm.isLong())) {
339 				int thisInt = ((PrologNumber) thisTerm).getIntegerValue();
340 				int otherInt = ((PrologNumber) otherTerm).getIntegerValue();
341 				return thisInt == otherInt;
342 			}
343 			return thisTerm.equals(otherTerm);
344 		}
345 
346 		else {
347 
348 			int thisArity = thisTerm.getArity();
349 			int otherArity = otherTerm.getArity();
350 			String thisFunctor = thisTerm.getFunctor();
351 			String otherFunctor = otherTerm.getFunctor();
352 
353 			if (thisFunctor.equals(otherFunctor) && thisArity == otherArity) {
354 				for (int i = 0; i < thisArity; i++) {
355 					if (thisTerm.getArgument(i) != null && otherTerm.getArgument(i) != null) {
356 						if (!((AbstractTerm) thisTerm.getArgument(i)).unify(otherTerm.getArgument(i), stack)) {
357 							return false;
358 						}
359 					}
360 				}
361 				return true;
362 			}
363 		}
364 		return false;
365 	}
366 
367 }