// @(#)$Id: StringOfObject.java,v 1.33 2006/02/17 01:21:47 chalin Exp $ // Copyright (C) 2005 Iowa State University // // This file is part of the runtime library of the Java Modeling Language. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation; either version 2.1, // of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with JML; see the file LesserGPL.txt. If not, write to the Free // Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA // 02110-1301 USA. package org.jmlspecs.models.resolve; import java.util.Collection; import java.util.Iterator; import org.jmlspecs.models.*; /** Sequences of non-null object identities. Objects in the sequence * are not cloned coming in or going out, and this type uses "==" to * compare object identities. * * <p>The names in this type are based prnimarily * on the RESOLVE design, but are also chosen to be understandable to * someone familiar with {@link java.util.Collection}. However, note that * none of the operations does any mutation. * * <p>Many of the specifications are translated from William Ogden's * notes for CIS 680 at Ohio State University. (Thanks Bill!) This * is especially true for the ones specified recursively. Specifications * written using the {@link #int_size} and {@link #get} operations are an * alternative, and often given in the redundant parts. * * @version $Revision: 1.33 $ * @author Gary T. Leavens * @see org.jmlspecs.models.JMLObjectSequence * @see org.jmlspecs.models.JMLCollection * @see TotallyOrderedCompareTo */ public /*@ pure @*/ class StringOfObject implements JMLCollection { //@ public invariant owner == null; //@ public invariant !containsNull; /*@ public invariant @ (\forall Object o; o != null && has(o); @ \typeof(o) <: elementType); @*/ /** The sequence of objects that represent this string of objects. */ protected final /*@ spec_public non_null @*/ JMLObjectSequence elements; //@ in objectState; //@ protected invariant !elements.containsNull; // ********************** constructors ************************ /** Initialize this object to be empty string of objects. * @see #EMPTY * @see #StringOfObject(Object) */ /*@ public normal_behavior @ assignable objectState, owner, containsNull, elementType; @ ensures elements.isEmpty(); @*/ public StringOfObject () { //@ set owner = null; //@ set containsNull = false; //@ set elementType = \type(Object); elements = new JMLObjectSequence(); } /** Message used in exceptions. */ private static final String ILLEGAL_ARG_MSG = "Attempt to add null to a StringOfObject"; /** Initialize this object to string containing just the given object. * @see #StringOfObject() * @see #singleton(Object) */ /*@ public normal_behavior @ requires elem != null; @ assignable objectState, owner, containsNull, elementType; @ ensures elements.equals(new JMLObjectSequence(elem)); @*/ //@ implies_that //@ requires elem != null; public StringOfObject (/*@ non_null @*/ Object elem) throws IllegalArgumentException { //@ set owner = null; //@ set containsNull = false; //@ set elementType = \typeof(elem); if (elem != null) { elements = new JMLObjectSequence(elem); //@ assume !elements.containsNull; } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } /** Initialize this object from the given representation. */ /*@ protected normal_behavior @ requires os != null && !os.containsNull && os.owner == null; @ assignable objectState, owner, containsNull, elementType; @ ensures elements != null && elements.equals(os); @*/ //@ implies_that //@ requires os != null && !os.containsNull; protected StringOfObject (/*@ non_null @*/ JMLObjectSequence os) throws IllegalArgumentException { //@ set owner = null; //@ set containsNull = false; //@ set elementType = os.elementType; elements = os; } // *************** Static Constants and Factory Methods ************ /** The empty string of objects. * @see #StringOfObject() */ public static final /*@ non_null @*/ StringOfObject EMPTY = new StringOfObject(); //@ public invariant_redundantly EMPTY != null && EMPTY.isEmpty(); //@ public constraint_redundantly EMPTY == \old(EMPTY); /** Make a singleton string of objects containing just the given object. * @see #StringOfObject(Object) */ /*@ public normal_behavior @ requires elem != null; @ ensures \result != null @ && \result.int_size() == 1 && \result.get(0) == elem; @*/ public static /*@ pure @*/ /*@ non_null @*/ StringOfObject singleton(/*@ non_null @*/ Object elem) throws IllegalArgumentException { return new StringOfObject(elem); } /** Extend the given string by placing the given element at the end. * @see #ext(Object) * @see #add(Object) * @see #addFront(Object) * @see #addAfterIndex * @see #addBeforeIndex */ /*@ public normal_behavior @ requires s != null && elem != null; @ ensures \result != null @ && \result.elements.equals(s.elements.insertBack(elem)); @*/ public static /*@ pure @*/ /*@ non_null @*/ StringOfObject ext(/*@ non_null @*/ StringOfObject s, /*@ non_null @*/ Object elem) throws IllegalArgumentException { return s.ext(elem); } /** Make an array of objects into a string. * @see #from(Collection) * @see #addAll(Object[]) */ /*@ public normal_behavior @ requires \nonnullelements(a); @ ensures \result != null && \result.equals(EMPTY.addAll(a)); @*/ public static /*@ pure @*/ /*@ non_null @*/ StringOfObject from(/*@ non_null @*/ Object[] a) throws IllegalArgumentException { return EMPTY.addAll(a); } /** Make a collection into a string. * @see #from(Object[]) * @see #addAll(Collection) */ /*@ public normal_behavior @ requires c != null && !c.contains(null); @ ensures \result != null && \result.equals(EMPTY.addAll(c)); @*/ public static /*@ pure @*/ /*@ non_null @*/ StringOfObject from(/*@ non_null @*/ Collection c) throws IllegalArgumentException { return EMPTY.addAll(c); } /** Compose all the elements of <kbd>a</kbd> in order. * @see #productFrom(StringOfObject[], int) * @see #productFromTo(StringOfObject[], int, int) * @see #composedWith(StringOfObject) */ /*@ public normal_behavior @ requires \nonnullelements(a); @ ensures \result.equals(productFrom(a, 0)); @*/ //@ implies_that //@ requires \nonnullelements(a); public static /*@ pure @*/ /*@ non_null @*/ StringOfObject product(/*@ non_null @*/ StringOfObject[] a) { return productFrom(a, 0); } /** Compose all the elements of a in order, starting at the given index. * @see #productFrom(StringOfObject[], int) * @see #productFromTo(StringOfObject[], int, int) * @see #composedWith(StringOfObject) */ /*@ public normal_behavior @ requires \nonnullelements(a) @ && 0 <= fromIndex && fromIndex <= a.length; @ ensures \result.equals(productFromTo(a, fromIndex, a.length)); @*/ //@ implies_that /*@ requires \nonnullelements(a) @ && a != null && 0 <= fromIndex && fromIndex <= a.length; @*/ public static /*@ pure @*/ /*@ non_null @*/ StringOfObject productFrom(/*@ non_null @*/ StringOfObject[] a, int fromIndex) { return productFromTo(a, fromIndex, a.length); } /** Compose all the elements of a in order, starting with * <kbd>fromIndex</kbd>, and including elements up to but not * including <kbd>toIndex</kbd>. * @param fromIndex the index of the first string of elements to * compose with. * @param toIndex one past the index of the elements to compose with. * @see #productFrom(StringOfObject[], int) * @see #productFromTo(StringOfObject[], int, int) * @see #composedWith(StringOfObject) */ /*@ public normal_behavior @ requires \nonnullelements(a) @ && a != null && 0 <= fromIndex && fromIndex <= toIndex @ && toIndex <= a.length; @ {| @ requires fromIndex == toIndex; @ ensures \result != null && \result.isEmpty(); @ also @ requires fromIndex < toIndex; @ ensures \result != null @ && \result.equals(new StringOfObject(a[fromIndex]) @ .concat(productFromTo(a, (int)(fromIndex+1), @ toIndex))); @ |} @*/ //@ implies_that /*@ requires \nonnullelements(a) @ && a != null && 0 <= fromIndex && fromIndex <= toIndex @ && toIndex <= a.length; @*/ public static /*@ pure @*/ /*@ non_null @*/ StringOfObject productFromTo(/*@ non_null @*/ StringOfObject[] a, int fromIndex, int toIndex) { if (fromIndex == toIndex) { return EMPTY; } else { //@ assume a != null; //@ assert fromIndex < a.length; return new StringOfObject(a[fromIndex]) .concat(productFromTo(a, fromIndex+1, toIndex)); } } //@ nowarn Exception; // ***************** Observers ******************** /** Return the element in the string at the given index. */ /*@ public normal_behavior @ requires 0 <= index && index < elements.int_size(); @ ensures \result == elements.itemAt(index); @ also @ public exceptional_behavior @ requires !(0 <= index && index < elements.int_size()); @ signals_only IndexOutOfBoundsException; @ for_example @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)) && index == 0; @ ensures \result == x; @ also @ public exceptional_example @ requires this.isEmpty(); @ signals_only IndexOutOfBoundsException; @*/ public /*@ non_null @*/ Object get(int index) throws IndexOutOfBoundsException { return elements.itemAt(index); } //@ nowarn NonNullResult; /** Tell the size of this string. * @see #length() */ /*@ also @ public normal_behavior @ ensures \result == elements.int_size(); @ ensures_redundantly \result >= 0; @ implies_that @ public normal_behavior @ requires isEmpty(); @ ensures \result == 0; @ also @ public normal_behavior @ forall Object x; @ forall StringOfObject beta; @ requires beta != null && this.equals(beta.ext(x)); @ ensures \result == beta.int_size() + 1; @*/ public int int_size() { return elements.int_size(); } /** Tell the length (= size) of this string. * @see #int_size() */ /*@ public normal_behavior @ ensures \result == elements.int_size(); @ ensures_redundantly \result == int_size(); @ implies_that @ public normal_behavior @ requires isEmpty(); @ ensures \result == 0; @*/ public int length() { return int_size(); } /** Extend a string with a new element at the end. * @see #ext(StringOfObject, Object) * @see #add(Object) * @see #addFront(Object) * @see #addAfterIndex * @see #addBeforeIndex */ /*@ public normal_behavior @ requires elem != null; @ ensures \result != null @ && \result.elements.equals(this.elements.insertBack(elem)); @ implies_that @ public normal_behavior @ ensures \result != null && \result.int_size() == int_size() + 1 @ && (\forall int i; 0 <= i && i < int_size(); @ \result.get(i) == this.get(i)) @ && \result.get(this.int_size()) == elem; @ for_example @ public normal_example @ requires this.equals(EMPTY); @ ensures \result != null @ && \result.equals(new StringOfObject(elem)); @ also @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)); @ ensures \result != null && \result.int_size() == 2 @ && \result.get(0) == x && \result.get(1) == elem; @*/ public /*@ non_null @*/ StringOfObject ext(/*@ non_null @*/ Object elem) throws IllegalArgumentException { if (elem != null) { return new StringOfObject(elements.insertBack(elem));//@ nowarn Pre; } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } //@ nowarn Exception; /** Add an element to a string at the end. This is a synonym for ext. * @see #ext(Object) * @see #ext(StringOfObject, Object) * @see #addFront(Object) * @see #addAfterIndex * @see #addBeforeIndex */ /*@ public normal_behavior @ requires elem != null; @ ensures \result != null && \result.equals(this.ext(elem)); @*/ public /*@ non_null @*/ StringOfObject add(/*@ non_null @*/ Object elem) throws IllegalArgumentException { return ext(elem); } /** Add an element to a string at the front. * @see #add(Object) * @see #ext(Object) * @see #ext(StringOfObject, Object) * @see #addAfterIndex * @see #addBeforeIndex */ /*@ public normal_behavior @ ensures \result != null @ && \result.elements.equals(this.elements.insertFront(elem)); @ implies_that @ public normal_behavior @ ensures \result != null && \result.int_size() == int_size() + 1 @ && \result.get(0) == elem @ && (\forall int i; 0 <= i && i < int_size(); @ \result.get((int)(i+1)) == this.get(i)); @ for_example @ public normal_example @ requires this.equals(EMPTY); @ ensures \result != null @ && \result.equals(new StringOfObject(elem)); @ also @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)); @ ensures \result != null && \result.int_size() == 2 @ && \result.get(0) == elem && \result.get(1) == x; @*/ public /*@ non_null @*/ StringOfObject addFront(/*@ non_null @*/ Object elem) throws IllegalArgumentException { if (elem != null) { return new StringOfObject(elements.insertFront(elem)); //@ nowarn Pre; } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } //@ nowarn Exception; /** Add an element to a string after the given index. * @see #addBeforeIndex * @see #addFront(Object) * @see #add(Object) * @see #ext(Object) * @see #ext(StringOfObject, Object) */ /*@ public normal_behavior @ requires -1 <= afterThisOne && afterThisOne < int_size(); @ ensures \result != null @ && \result.elements.equals( @ this.elements.insertAfterIndex(afterThisOne,elem)); @ also @ public exceptional_behavior @ requires !(-1 <= afterThisOne && afterThisOne < int_size()); @ signals_only IndexOutOfBoundsException; @ for_example @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)) && afterThisOne == -1; @ ensures \result != null && \result.int_size() == 2 @ && \result.get(0) == elem && \result.get(1) == x; @ also @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)) && afterThisOne == 0; @ ensures \result != null && \result.int_size() == 2 @ && \result.get(0) == x && \result.get(1) == elem; @*/ public /*@ non_null @*/ StringOfObject addAfterIndex(int afterThisOne, /*@ non_null @*/ Object elem) throws IndexOutOfBoundsException, IllegalArgumentException { if (elem != null) { JMLObjectSequence os = elements.insertAfterIndex(afterThisOne,elem); //@ assume os != null && !os.containsNull; return new StringOfObject(os); } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } //@ nowarn Exception; /** Add an element to a string before the given index. * @see #addAfterIndex * @see #addFront(Object) * @see #add(Object) * @see #ext(Object) * @see #ext(StringOfObject, Object) */ /*@ public normal_behavior @ requires 0 <= beforeThisOne && beforeThisOne <= int_size(); @ ensures \result != null @ && \result.elements.equals( @ this.elements.insertBeforeIndex(beforeThisOne,elem)); @ also @ public exceptional_behavior @ requires !(0 <= beforeThisOne && beforeThisOne <= int_size()); @ signals_only IndexOutOfBoundsException; @ for_example @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)) && beforeThisOne == 0; @ ensures \result != null && \result.int_size() == 2 @ && \result.get(0) == elem && \result.get(1) == x; @ also @ public normal_example @ forall Object x; @ requires this.equals(new StringOfObject(x)) && beforeThisOne == 1; @ ensures \result != null && \result.int_size() == 2 @ && \result.get(0) == x && \result.get(1) == elem; @*/ public /*@ non_null @*/ StringOfObject addBeforeIndex(int beforeThisOne, /*@ non_null @*/ Object elem) throws IndexOutOfBoundsException, IllegalArgumentException { if (elem != null) { JMLObjectSequence os = elements.insertBeforeIndex(beforeThisOne,elem); //@ assume os != null && !os.containsNull; return new StringOfObject(os); } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } //@ nowarn Exception; /** Concatenate this with s. * @see #composedWith */ /*@ public normal_behavior @ requires s != null; @ {| @ requires s.isEmpty(); @ ensures \result != null && \result.equals(this); @ also @ requires !s.isEmpty(); @ ensures \result != null @ && (\forall StringOfObject beta; beta != null; @ (\forall Object x; s.equals(beta.ext(x)); @ \result.equals(this.concat(beta).ext(x)))); @ |} @*/ public /*@ non_null @*/ StringOfObject concat(/*@ non_null @*/ StringOfObject s) { return new StringOfObject(elements.concat(s.elements)); } //@ nowarn Exception; /** Compose this with s. This is a synonym for concat. * @see #concat */ /*@ public normal_behavior @ requires s != null; @ ensures \result != null && \result.equals(concat(s)); @*/ public /*@ non_null @*/ StringOfObject composedWith(/*@ non_null @*/ StringOfObject s) { return concat(s); } /** Add all the elements of c, at the end of this string, in the * order determined by c's iterator. * @see #addAll(Object[]) * @see #from(Collection) */ /*@ public normal_behavior @ requires c != null; @ ensures \result != null && \result.int_size() == this.int_size() + c.size() @ && (\forall int i; 0 <= i && i < \result.int_size(); @ (i < this.int_size() ==> \result.get(i) == this.get(i)) @ && (this.int_size() <= i @ ==> \result.get(i) @ == c.iterator().nthNextElement((int)(i - int_size())))); @ implies_that @ public normal_behavior @ requires c != null; @ ensures \result.equals(this); @*/ public /*@ non_null @*/ StringOfObject addAll(/*@ non_null @*/ Collection c) throws IllegalArgumentException { //@ assume c != null; JMLObjectSequence res = elements; Iterator i = c.iterator(); //@ loop_invariant !res.containsNull; while (i.hasNext()) { //@ nowarn LoopInv; Object o = i.next(); if (o != null) { res = res.insertBack(o); } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } return new StringOfObject(res); } //@ nowarn Exception; /** Add all the elements of c at the end of this string, in order * from the smallest to the largest index. * @see #addAll(Collection) */ /*@ public normal_behavior @ requires \nonnullelements(c); @ ensures \result != null && \result.int_size() == this.int_size() + c.length @ && (\forall int i; 0 <= i && i < \result.int_size(); @ (i < this.int_size() ==> \result.get(i) == this.get(i)) @ && (this.int_size() <= i @ ==> \result.get(i) == c[(int)(i - this.int_size())])); @*/ public /*@ non_null @*/ StringOfObject addAll(/*@ non_null @*/ Object[] c) throws IllegalArgumentException { //@ assume c != null; JMLObjectSequence res = elements; //@ loop_invariant !res.containsNull && 0 <= i && i <= c.length; //@ decreasing c.length - i; for (int i = 0; i < c.length; i++) { //@ nowarn LoopInv; Object o = c[i]; if (o != null) { res = res.insertBack(o); } else { throw new IllegalArgumentException(ILLEGAL_ARG_MSG); } } return new StringOfObject(res); } //@ nowarn Exception; /** Return the reverse of the string. That is, the same string * with the elements in the opposite order. * @see #reverse */ /*@ public normal_behavior @ ensures \result != null @ && \result.elements.equals(elements.reverse()); @ implies_that @ public normal_behavior @ ensures \result != null @ && \result.int_size() == this.int_size() @ && (\forall int i; 0 <= i && i < this.int_size(); @ this.get(i) == \result.get((int)(this.int_size()-i))); @ also @ public normal_behavior @ requires elements.isEmpty(); @ ensures \result != null && \result.equals(this); @ ensures \result != null && \result.isEmpty(); @ also @ public normal_behavior @ forall StringOfObject alpha; @ forall Object x; @ requires !elements.isEmpty(); @ ensures \result != null @ && (alpha != null && this.equals(alpha.ext(x)) @ ==> \result.equals(singleton(x).concat(alpha.rev()))); @*/ public /*@ non_null @*/ StringOfObject rev() { return new StringOfObject(elements.reverse()); } //@ nowarn Exception; /** Return the reverse of the string. That is, the same string * with the elements in the opposite order. * @see #rev */ /*@ public normal_behavior @ ensures \result != null && \result.equals(this.rev()); @*/ public /*@ non_null @*/ StringOfObject reverse() { return rev(); } /** Compose this string with itself the given number of times. * @see #composedWith */ /*@ public normal_behavior @ requires n >= 0; @ {| @ requires n == 0; @ ensures \result != null && \result.isEmpty(); @ also @ requires n > 0; @ ensures \result != null @ && \result.equals(this.pow((int)(n-1)).concat(this)); @ |} @*/ public /*@ non_null @*/ StringOfObject pow(int n) throws IllegalArgumentException { if (n < 0) { throw new IllegalArgumentException("Negative argument" + " to StringOfObejct.pow"); } JMLObjectSequence ret = JMLObjectSequence.EMPTY; //@ loop_invariant n >= 0 && ret != null && !ret.containsNull; //@ decreases n; while (n > 0) { //@ nowarn LoopInv; ret = ret.concat(elements); n--; } //@ assume ret != null && !ret.containsNull; return new StringOfObject(ret); } // *************** Comparison Operations *************************** /** Tell if this object is equal to the given argument. */ /*@ also @ public normal_behavior @ ensures \result @ ==> x != null && x instanceof StringOfObject @ && int_size() == ((StringOfObject)x).int_size() @ && (\forall int i; 0 <= i && i < int_size(); @ get(i) == ((StringOfObject)x).get(i)); @*/ public boolean equals(/*@ nullable @*/ Object x) { if (x == null || !(x instanceof StringOfObject)) { return false; } else { return elements.equals(((StringOfObject)x).elements); } } public Object clone() { return this; } /** Tell how many times the given object appears in this string. */ /*@ public normal_behavior @ {| @ requires isEmpty(); @ ensures \result == 0; @ also @ forall StringOfObject alpha; @ requires alpha != null && this.equals(alpha.ext(y)); @ ensures \result == alpha.occurs_ct(y) + 1; @ also @ forall StringOfObject alpha; @ forall Object x; @ requires alpha != null && this.equals(alpha.ext(x)) @ && x != y; @ ensures \result == alpha.occurs_ct(y); @ |} @ implies_that @ public normal_behavior @ ensures \result @ == (\num_of int i; 0 <= i && i < int_size(); get(i) == y); @ @*/ public int occurs_ct(Object y) { return elements.count(y); } // specification and documentation inherited public boolean has(/*@ nullable @*/ Object elem) { return elements.has(elem); } /** Tell whether this string is empty. * @see #int_size() * @see #length() */ /*@ public normal_behavior @ ensures \result <==> elements.isEmpty(); @ ensures_redundantly \result <==> this.equals(EMPTY); @*/ public boolean isEmpty() { return elements.isEmpty(); } // specification and documentation inherited public int hashCode() { return elements.hashCode(); } // specification and documentation inherited public String toString() { return elements.toString(); } /** Return an iterator for this string of objects. * @see #elements() */ public JMLIterator iterator() { return elements.iterator(); } //@ nowarn Post; /** Return an enumerator for this string of objects. * @see #iterator() */ /*@ public normal_behavior @ ensures \result != null @ && elements.equals(\result.uniteratedElems); @*/ public JMLObjectSequenceEnumerator elements() { return elements.elements(); } /** Tell whether this string occurs as a prefix of the given * argument string. * @see #isProperPrefix * @see #isSuffix */ /*@ public normal_behavior @ requires s2 != null; @ ensures \result <==> @ (\exists StringOfObject s; s != null; @ this.concat(s).equals(s2)); @ ensures_redundantly \result <==> elements.isPrefix(s2.elements); @*/ public boolean isPrefix(/*@ non_null @*/ StringOfObject s2) { return elements.isPrefix(s2.elements); } /** Tell whether this string occurs as a proper prefix of the given * argument string. * @see #isPrefix * @see #isProperSuffix */ /*@ public normal_behavior @ requires s2 != null; @ ensures \result <==> @ (\exists StringOfObject s; s != null && s.int_size() > 0; @ this.concat(s).equals(s2)); @ ensures_redundantly \result @ <==> elements.isProperPrefix(s2.elements); @*/ public boolean isProperPrefix(/*@ non_null @*/ StringOfObject s2) { return elements.isProperPrefix(s2.elements); } /** Tell whether this string occurs as a proper suffix of the given * argument string. * @see #isSuffix * @see #isProperPrefix */ /*@ public normal_behavior @ requires s2 != null; @ ensures \result <==> @ (\exists StringOfObject r; r != null && r.int_size() > 0; @ r.concat(this).equals(s2)); @ ensures_redundantly \result @ <==> elements.isProperSuffix(s2.elements); @*/ public boolean isProperSuffix(/*@ non_null @*/ StringOfObject s2) { return elements.isProperSuffix(s2.elements); } /** Tell whether this string occurs as a suffix of the given * argument string. * @see #isProperSuffix * @see #isPrefix */ /*@ public normal_behavior @ requires s2 != null; @ ensures \result <==> @ (\exists StringOfObject r; r != null; @ r.concat(this).equals(s2)); @ ensures_redundantly \result <==> elements.isSuffix(s2.elements); @*/ public boolean isSuffix(/*@ non_null @*/ StringOfObject s2) { return elements.isSuffix(s2.elements); } }