diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml deleted file mode 100644 index 98ee6c1..0000000 --- a/.github/workflows/pipeline.yml +++ /dev/null @@ -1,74 +0,0 @@ -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Java CI with Maven - -on: - push: - # branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - # old-school build and jar method. No tests run or compiled. - build-1_6: - runs-on: ubuntu-16.04 - strategy: - matrix: - # build for java 1.6, however don't run any tests - java: [ 1.6 ] - name: Java ${{ matrix.java }} - steps: - - uses: actions/checkout@v2 - - name: Setup java - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java }} - - name: Compile Java ${{ matrix.java }} - run: | - mkdir -p target/classes - javac -d target/classes/ src/main/java/org/json/*.java - - name: Create java ${{ matrix.java }} JAR - run: | - jar cvf target/org.json.jar -C target/classes . - - name: Upload Java ${{ matrix.java }} JAR - uses: actions/upload-artifact@v1 - with: - name: Java ${{ matrix.java }} JAR - path: target/org.json.jar - - build: - runs-on: ubuntu-16.04 - strategy: - matrix: - # build against supported Java LTS versions: - java: [ 1.7, 8, 11 ] - name: Java ${{ matrix.java }} - steps: - - uses: actions/checkout@v2 - - name: Setup java - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java }} - - name: Compile Java ${{ matrix.java }} - run: mvn clean compile -Dmaven.compiler.source=${{ matrix.java }} -Dmaven.compiler.target=${{ matrix.java }} -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true - - name: Run Tests ${{ matrix.java }} - run: | - mvn test -Dmaven.compiler.source=${{ matrix.java }} -Dmaven.compiler.target=${{ matrix.java }} - - name: Build Test Report ${{ matrix.java }} - if: ${{ always() }} - run: | - mvn surefire-report:report-only -Dmaven.compiler.source=${{ matrix.java }} -Dmaven.compiler.target=${{ matrix.java }} - mvn site -DgenerateReports=false -Dmaven.compiler.source=${{ matrix.java }} -Dmaven.compiler.target=${{ matrix.java }} - - name: Upload Test Results ${{ matrix.java }} - if: ${{ always() }} - uses: actions/upload-artifact@v1 - with: - name: Test Results ${{ matrix.java }} - path: target/surefire-reports/ - - name: Upload Test Report ${{ matrix.java }} - if: ${{ always() }} - uses: actions/upload-artifact@v1 - with: - name: Test Report ${{ matrix.java }} - path: target/site/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7794c4c..50b216e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,3 @@ # ignore Intellij Idea project files .idea *.iml -/target/ - -/bin/ -build -.settings/ -/.gradle/ -/gradle/ -/gradlew -/gradlew.bat -.gitmodules diff --git a/src/main/java/org/json/CDL.java b/CDL.java similarity index 95% rename from src/main/java/org/json/CDL.java rename to CDL.java index f12cfc0..6bc41dc 100644 --- a/src/main/java/org/json/CDL.java +++ b/CDL.java @@ -98,7 +98,7 @@ public class CDL { * Produce a JSONArray of strings from a row of comma delimited values. * @param x A JSONTokener of the source text. * @return A JSONArray of strings. - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { JSONArray ja = new JSONArray(); @@ -134,7 +134,7 @@ public class CDL { * method. * @param x A JSONTokener of the source text. * @return A JSONObject combining the names and values. - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) throws JSONException { @@ -184,7 +184,7 @@ public class CDL { * using the first row as a source of names. * @param string The comma delimited text. * @return A JSONArray of JSONObjects. - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONArray toJSONArray(String string) throws JSONException { return toJSONArray(new JSONTokener(string)); @@ -195,7 +195,7 @@ public class CDL { * using the first row as a source of names. * @param x The JSONTokener containing the comma delimited text. * @return A JSONArray of JSONObjects. - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONArray toJSONArray(JSONTokener x) throws JSONException { return toJSONArray(rowToJSONArray(x), x); @@ -207,7 +207,7 @@ public class CDL { * @param names A JSONArray of strings. * @param string The comma delimited text. * @return A JSONArray of JSONObjects. - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONArray toJSONArray(JSONArray names, String string) throws JSONException { @@ -220,7 +220,7 @@ public class CDL { * @param names A JSONArray of strings. * @param x A JSONTokener of the source text. * @return A JSONArray of JSONObjects. - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONArray toJSONArray(JSONArray names, JSONTokener x) throws JSONException { @@ -248,7 +248,7 @@ public class CDL { * JSONObject. * @param ja A JSONArray of JSONObjects. * @return A comma delimited text. - * @throws JSONException if a called function fails + * @throws JSONException */ public static String toString(JSONArray ja) throws JSONException { JSONObject jo = ja.optJSONObject(0); @@ -268,7 +268,7 @@ public class CDL { * @param names A JSONArray of strings. * @param ja A JSONArray of JSONObjects. * @return A comma delimited text. - * @throws JSONException if a called function fails + * @throws JSONException */ public static String toString(JSONArray names, JSONArray ja) throws JSONException { diff --git a/src/main/java/org/json/Cookie.java b/Cookie.java similarity index 55% rename from src/main/java/org/json/Cookie.java rename to Cookie.java index a43d1ed..348dc68 100644 --- a/src/main/java/org/json/Cookie.java +++ b/Cookie.java @@ -1,7 +1,5 @@ package org.json; -import java.util.Locale; - /* Copyright (c) 2002 JSON.org @@ -29,7 +27,6 @@ SOFTWARE. /** * Convert a web browser cookie specification to a JSONObject and back. * JSON and Cookies are both notations for name/value pairs. - * See also: https://tools.ietf.org/html/rfc6265 * @author JSON.org * @version 2015-12-09 */ @@ -68,65 +65,41 @@ public class Cookie { /** * Convert a cookie specification string into a JSONObject. The string - * must contain a name value pair separated by '='. The name and the value + * will contain a name value pair separated by '='. The name and the value * will be unescaped, possibly converting '+' and '%' sequences. The * cookie properties may follow, separated by ';', also represented as - * name=value (except the Attribute properties like "Secure" or "HttpOnly", - * which do not have a value. The value {@link Boolean#TRUE} will be used for these). + * name=value (except the secure property, which does not have a value). * The name will be stored under the key "name", and the value will be * stored under the key "value". This method does not do checking or * validation of the parameters. It only converts the cookie string into - * a JSONObject. All attribute names are converted to lower case keys in the - * JSONObject (HttpOnly => httponly). If an attribute is specified more than - * once, only the value found closer to the end of the cookie-string is kept. + * a JSONObject. * @param string The cookie specification string. * @return A JSONObject containing "name", "value", and possibly other * members. - * @throws JSONException If there is an error parsing the Cookie String. - * Cookie strings must have at least one '=' character and the 'name' - * portion of the cookie must not be blank. + * @throws JSONException */ - public static JSONObject toJSONObject(String string) { - final JSONObject jo = new JSONObject(); + public static JSONObject toJSONObject(String string) throws JSONException { String name; + JSONObject jo = new JSONObject(); Object value; - - JSONTokener x = new JSONTokener(string); - - name = unescape(x.nextTo('=').trim()); - //per RFC6265, if the name is blank, the cookie should be ignored. - if("".equals(name)) { - throw new JSONException("Cookies must have a 'name'"); - } - jo.put("name", name); - // per RFC6265, if there is no '=', the cookie should be ignored. - // the 'next' call here throws an exception if the '=' is not found. + jo.put("name", x.nextTo('=')); x.next('='); - jo.put("value", unescape(x.nextTo(';')).trim()); - // discard the ';' + jo.put("value", x.nextTo(';')); x.next(); - // parse the remaining cookie attributes while (x.more()) { - name = unescape(x.nextTo("=;")).trim().toLowerCase(Locale.ROOT); - // don't allow a cookies attributes to overwrite it's name or value. - if("name".equalsIgnoreCase(name)) { - throw new JSONException("Illegal attribute name: 'name'"); - } - if("value".equalsIgnoreCase(name)) { - throw new JSONException("Illegal attribute name: 'value'"); - } - // check to see if it's a flag property + name = unescape(x.nextTo("=;")); if (x.next() != '=') { - value = Boolean.TRUE; + if (name.equals("secure")) { + value = Boolean.TRUE; + } else { + throw x.syntaxError("Missing '=' in cookie parameter."); + } } else { - value = unescape(x.nextTo(';')).trim(); + value = unescape(x.nextTo(';')); x.next(); } - // only store non-blank attributes - if(!"".equals(name) && !"".equals(value)) { - jo.put(name, value); - } + jo.put(name, value); } return jo; } @@ -134,63 +107,35 @@ public class Cookie { /** * Convert a JSONObject into a cookie specification string. The JSONObject - * must contain "name" and "value" members (case insensitive). - * If the JSONObject contains other members, they will be appended to the cookie - * specification string. User-Agents are instructed to ignore unknown attributes, - * so ensure your JSONObject is using only known attributes. - * See also: https://tools.ietf.org/html/rfc6265 + * must contain "name" and "value" members. + * If the JSONObject contains "expires", "domain", "path", or "secure" + * members, they will be appended to the cookie specification string. + * All other members are ignored. * @param jo A JSONObject * @return A cookie specification string - * @throws JSONException thrown if the cookie has no name. + * @throws JSONException */ public static String toString(JSONObject jo) throws JSONException { StringBuilder sb = new StringBuilder(); - - String name = null; - Object value = null; - for(String key : jo.keySet()){ - if("name".equalsIgnoreCase(key)) { - name = jo.getString(key).trim(); - } - if("value".equalsIgnoreCase(key)) { - value=jo.getString(key).trim(); - } - if(name != null && value != null) { - break; - } - } - - if(name == null || "".equals(name.trim())) { - throw new JSONException("Cookie does not have a name"); - } - if(value == null) { - value = ""; - } - - sb.append(escape(name)); + + sb.append(escape(jo.getString("name"))); sb.append("="); - sb.append(escape((String)value)); - - for(String key : jo.keySet()){ - if("name".equalsIgnoreCase(key) - || "value".equalsIgnoreCase(key)) { - // already processed above - continue; - } - value = jo.opt(key); - if(value instanceof Boolean) { - if(Boolean.TRUE.equals(value)) { - sb.append(';').append(escape(key)); - } - // don't emit false values - } else { - sb.append(';') - .append(escape(key)) - .append('=') - .append(escape(value.toString())); - } + sb.append(escape(jo.getString("value"))); + if (jo.has("expires")) { + sb.append(";expires="); + sb.append(jo.getString("expires")); + } + if (jo.has("domain")) { + sb.append(";domain="); + sb.append(escape(jo.getString("domain"))); + } + if (jo.has("path")) { + sb.append(";path="); + sb.append(escape(jo.getString("path"))); + } + if (jo.optBoolean("secure")) { + sb.append(";secure"); } - return sb.toString(); } diff --git a/src/main/java/org/json/CookieList.java b/CookieList.java similarity index 96% rename from src/main/java/org/json/CookieList.java rename to CookieList.java index 83b2630..c67ee3a 100644 --- a/src/main/java/org/json/CookieList.java +++ b/CookieList.java @@ -42,7 +42,7 @@ public class CookieList { * cookieJSONObject.getString("value")); * @param string A cookie list string * @return A JSONObject - * @throws JSONException if a called function fails + * @throws JSONException */ public static JSONObject toJSONObject(String string) throws JSONException { JSONObject jo = new JSONObject(); @@ -63,7 +63,7 @@ public class CookieList { * in the names and values are replaced by "%hh". * @param jo A JSONObject * @return A cookie list string - * @throws JSONException if a called function fails + * @throws JSONException */ public static String toString(JSONObject jo) throws JSONException { boolean b = false; diff --git a/src/main/java/org/json/HTTP.java b/HTTP.java similarity index 94% rename from src/main/java/org/json/HTTP.java rename to HTTP.java index cc01167..70b88ee 100644 --- a/src/main/java/org/json/HTTP.java +++ b/HTTP.java @@ -1,162 +1,162 @@ -package org.json; - -/* -Copyright (c) 2002 JSON.org - -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 shall be used for Good, not Evil. - -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. -*/ - -import java.util.Locale; - -/** - * Convert an HTTP header to a JSONObject and back. - * @author JSON.org - * @version 2015-12-09 - */ -public class HTTP { - - /** Carriage return/line feed. */ - public static final String CRLF = "\r\n"; - - /** - * Convert an HTTP header string into a JSONObject. It can be a request - * header or a response header. A request header will contain - *
{ - * Method: "POST" (for example), - * "Request-URI": "/" (for example), - * "HTTP-Version": "HTTP/1.1" (for example) - * }- * A response header will contain - *
{ - * "HTTP-Version": "HTTP/1.1" (for example), - * "Status-Code": "200" (for example), - * "Reason-Phrase": "OK" (for example) - * }- * In addition, the other parameters in the header will be captured, using - * the HTTP field names as JSON names, so that
{@code - * Date: Sun, 26 May 2002 18:06:04 GMT - * Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s - * Cache-Control: no-cache}- * become - *
{@code - * Date: "Sun, 26 May 2002 18:06:04 GMT", - * Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s", - * "Cache-Control": "no-cache", - * ...}- * It does no further checking or conversion. It does not parse dates. - * It does not do '%' transforms on URLs. - * @param string An HTTP header string. - * @return A JSONObject containing the elements and attributes - * of the XML string. - * @throws JSONException if a called function fails - */ - public static JSONObject toJSONObject(String string) throws JSONException { - JSONObject jo = new JSONObject(); - HTTPTokener x = new HTTPTokener(string); - String token; - - token = x.nextToken(); - if (token.toUpperCase(Locale.ROOT).startsWith("HTTP")) { - -// Response - - jo.put("HTTP-Version", token); - jo.put("Status-Code", x.nextToken()); - jo.put("Reason-Phrase", x.nextTo('\0')); - x.next(); - - } else { - -// Request - - jo.put("Method", token); - jo.put("Request-URI", x.nextToken()); - jo.put("HTTP-Version", x.nextToken()); - } - -// Fields - - while (x.more()) { - String name = x.nextTo(':'); - x.next(':'); - jo.put(name, x.nextTo('\0')); - x.next(); - } - return jo; - } - - - /** - * Convert a JSONObject into an HTTP header. A request header must contain - *
{ - * Method: "POST" (for example), - * "Request-URI": "/" (for example), - * "HTTP-Version": "HTTP/1.1" (for example) - * }- * A response header must contain - *
{ - * "HTTP-Version": "HTTP/1.1" (for example), - * "Status-Code": "200" (for example), - * "Reason-Phrase": "OK" (for example) - * }- * Any other members of the JSONObject will be output as HTTP fields. - * The result will end with two CRLF pairs. - * @param jo A JSONObject - * @return An HTTP header string. - * @throws JSONException if the object does not contain enough - * information. - */ - public static String toString(JSONObject jo) throws JSONException { - StringBuilder sb = new StringBuilder(); - if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { - sb.append(jo.getString("HTTP-Version")); - sb.append(' '); - sb.append(jo.getString("Status-Code")); - sb.append(' '); - sb.append(jo.getString("Reason-Phrase")); - } else if (jo.has("Method") && jo.has("Request-URI")) { - sb.append(jo.getString("Method")); - sb.append(' '); - sb.append('"'); - sb.append(jo.getString("Request-URI")); - sb.append('"'); - sb.append(' '); - sb.append(jo.getString("HTTP-Version")); - } else { - throw new JSONException("Not enough material for an HTTP header."); - } - sb.append(CRLF); - // Don't use the new entrySet API to maintain Android support - for (final String key : jo.keySet()) { - String value = jo.optString(key); - if (!"HTTP-Version".equals(key) && !"Status-Code".equals(key) && - !"Reason-Phrase".equals(key) && !"Method".equals(key) && - !"Request-URI".equals(key) && !JSONObject.NULL.equals(value)) { - sb.append(key); - sb.append(": "); - sb.append(jo.optString(key)); - sb.append(CRLF); - } - } - sb.append(CRLF); - return sb.toString(); - } -} +package org.json; + +/* +Copyright (c) 2002 JSON.org + +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 shall be used for Good, not Evil. + +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. +*/ + +import java.util.Locale; + +/** + * Convert an HTTP header to a JSONObject and back. + * @author JSON.org + * @version 2015-12-09 + */ +public class HTTP { + + /** Carriage return/line feed. */ + public static final String CRLF = "\r\n"; + + /** + * Convert an HTTP header string into a JSONObject. It can be a request + * header or a response header. A request header will contain + *
{ + * Method: "POST" (for example), + * "Request-URI": "/" (for example), + * "HTTP-Version": "HTTP/1.1" (for example) + * }+ * A response header will contain + *
{ + * "HTTP-Version": "HTTP/1.1" (for example), + * "Status-Code": "200" (for example), + * "Reason-Phrase": "OK" (for example) + * }+ * In addition, the other parameters in the header will be captured, using + * the HTTP field names as JSON names, so that
+ * Date: Sun, 26 May 2002 18:06:04 GMT + * Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s + * Cache-Control: no-cache+ * become + *
{... + * Date: "Sun, 26 May 2002 18:06:04 GMT", + * Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s", + * "Cache-Control": "no-cache", + * ...}+ * It does no further checking or conversion. It does not parse dates. + * It does not do '%' transforms on URLs. + * @param string An HTTP header string. + * @return A JSONObject containing the elements and attributes + * of the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + HTTPTokener x = new HTTPTokener(string); + String token; + + token = x.nextToken(); + if (token.toUpperCase(Locale.ROOT).startsWith("HTTP")) { + +// Response + + jo.put("HTTP-Version", token); + jo.put("Status-Code", x.nextToken()); + jo.put("Reason-Phrase", x.nextTo('\0')); + x.next(); + + } else { + +// Request + + jo.put("Method", token); + jo.put("Request-URI", x.nextToken()); + jo.put("HTTP-Version", x.nextToken()); + } + +// Fields + + while (x.more()) { + String name = x.nextTo(':'); + x.next(':'); + jo.put(name, x.nextTo('\0')); + x.next(); + } + return jo; + } + + + /** + * Convert a JSONObject into an HTTP header. A request header must contain + *
{ + * Method: "POST" (for example), + * "Request-URI": "/" (for example), + * "HTTP-Version": "HTTP/1.1" (for example) + * }+ * A response header must contain + *
{ + * "HTTP-Version": "HTTP/1.1" (for example), + * "Status-Code": "200" (for example), + * "Reason-Phrase": "OK" (for example) + * }+ * Any other members of the JSONObject will be output as HTTP fields. + * The result will end with two CRLF pairs. + * @param jo A JSONObject + * @return An HTTP header string. + * @throws JSONException if the object does not contain enough + * information. + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { + sb.append(jo.getString("HTTP-Version")); + sb.append(' '); + sb.append(jo.getString("Status-Code")); + sb.append(' '); + sb.append(jo.getString("Reason-Phrase")); + } else if (jo.has("Method") && jo.has("Request-URI")) { + sb.append(jo.getString("Method")); + sb.append(' '); + sb.append('"'); + sb.append(jo.getString("Request-URI")); + sb.append('"'); + sb.append(' '); + sb.append(jo.getString("HTTP-Version")); + } else { + throw new JSONException("Not enough material for an HTTP header."); + } + sb.append(CRLF); + // Don't use the new entrySet API to maintain Android support + for (final String key : jo.keySet()) { + String value = jo.optString(key); + if (!"HTTP-Version".equals(key) && !"Status-Code".equals(key) && + !"Reason-Phrase".equals(key) && !"Method".equals(key) && + !"Request-URI".equals(key) && !JSONObject.NULL.equals(value)) { + sb.append(key); + sb.append(": "); + sb.append(jo.optString(key)); + sb.append(CRLF); + } + } + sb.append(CRLF); + return sb.toString(); + } +} diff --git a/src/main/java/org/json/HTTPTokener.java b/HTTPTokener.java similarity index 97% rename from src/main/java/org/json/HTTPTokener.java rename to HTTPTokener.java index 16c7081..55f48ff 100644 --- a/src/main/java/org/json/HTTPTokener.java +++ b/HTTPTokener.java @@ -43,8 +43,8 @@ public class HTTPTokener extends JSONTokener { /** * Get the next token or string. This is used in parsing HTTP headers. + * @throws JSONException * @return A String. - * @throws JSONException if a syntax error occurs */ public String nextToken() throws JSONException { char c; diff --git a/src/main/java/org/json/JSONArray.java b/JSONArray.java similarity index 94% rename from src/main/java/org/json/JSONArray.java rename to JSONArray.java index 3082974..8847c5d 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/JSONArray.java @@ -173,7 +173,9 @@ public class JSONArray implements Iterable