diff --git a/README.md b/README.md index cfb5d92f..36b85c90 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ * Version: 0.0.0 * JDK 1.6 Compatibility is required. -* [Developer Guide](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/DeveloperGuide) +* [Developer Guide](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/) * [Javadoc](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/javadoc/) * [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) @@ -22,7 +22,7 @@ the DSA architecture, please visit ## Link Development -Please read the [developer guide](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/DeveloperGuide). +Please read the [developer guide](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/). ## Acknowledgements diff --git a/build.gradle b/build.gradle index 1d3d3c99..ea4d4ca3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'java' apply plugin: 'maven' group 'org.iot.dsa' -version '0.20.0' +version '0.21.0' sourceCompatibility = 1.6 targetCompatibility = 1.6 diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java index 1778b567..01791a4f 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java @@ -215,14 +215,15 @@ protected void encodeChild(ApiObject child, MessageWriter writer) { map.put("$is", "node"); } if (child.isAction()) { - ActionSpec action = child.getAction(); e = cacheMap.remove("$invokable"); if (e != null) { map.put("$invokable", e); } else { if (child.isAdmin()) { map.put("$invokable", DSPermission.CONFIG.toString()); - } else if (!child.isReadOnly()) { + } else if (child.isReadOnly()) { + map.put("$invokable", DSPermission.READ.toString()); + } else { map.put("$invokable", DSPermission.WRITE.toString()); } } @@ -308,13 +309,15 @@ private void encodeTargetAction(ApiObject object, MessageWriter writer) { if (e == null) { if (object.isAdmin()) { encode("$invokable", DSPermission.CONFIG.toString(), writer); - } else if (!object.isReadOnly()) { + } else if (object.isReadOnly()) { + encode("$invokable", DSPermission.READ.toString(), writer); + } else { encode("$invokable", DSPermission.WRITE.toString(), writer); } } else { encode("$invokable", e, writer); } - e = cacheMap.remove("params"); + e = cacheMap.remove("$params"); if (e == null) { DSList list = new DSList(); Iterator params = action.getParameters(); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1ConnectionInit.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1ConnectionInit.java index fe1b302f..16ee33d0 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1ConnectionInit.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1ConnectionInit.java @@ -29,8 +29,8 @@ public class DS1ConnectionInit extends DSNode { /////////////////////////////////////////////////////////////////////////// private static final String DSA_VERSION = "1.1.2"; - //private static final String[] SUPPORTED_FORMATS = new String[]{"msgpack", "json"}; - private static final String[] SUPPORTED_FORMATS = new String[]{"json"}; + private static final String[] SUPPORTED_FORMATS = new String[]{"msgpack", "json"}; + //private static final String[] SUPPORTED_FORMATS = new String[]{"json"}; private String BROKER_REQ = "Broker Request"; private String BROKER_RES = "Broker Response"; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java index d2ad81ce..3acf852d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java @@ -118,7 +118,9 @@ protected void doRecvMessage() throws IOException { DSIReader reader = getReader(); switch (reader.next()) { case BEGIN_MAP: + getTransport().beginRecvMessage(); processEnvelope(reader); + getTransport().endRecvMessage(); break; case END_MAP: case END_LIST: diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java index 2a7655e7..85a38e7c 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java @@ -1,11 +1,6 @@ package org.iot.dsa.dslink; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.URL; import java.util.Arrays; import java.util.Calendar; @@ -24,18 +19,14 @@ import org.iot.dsa.util.DSException; /** - * Represents an upstream connection, a node tree, and manages the lifecycle of both. + * The root node of a DSLink node tree with two children: main and sys. Main is the root + * data or application node. Sys contains various services such as the upstream + * connection to the broker. *

+ * This node can and should be used as the main class for launching a link process. *

- *

- * Links are created with DSLinkConfig object. The main method of the process is responsible for - * creating the config. After instantiation, the link should call DSLink.run() - *

- *

- *

- * Lifecycle: - *

- * TODO + * This node can be subclassed for specialized purposes. Testing uses a version that + * creates a special transport for sending and receiving messages to itself. * * @author Aaron Hansen */ @@ -199,8 +190,8 @@ public static DSLink load(DSLinkConfig config) { } /** - * This is a convenience for DSLink.load(new DSLinkConfig(args)).run() and can be used as the - * the main class for any link. Use DSLink.shutdown() to stop running. + * This is a convenience for DSLink.load(new DSLinkConfig(args)).run() and should be + * used as the the main class for any link. Use DSLink.shutdown() to stop running. */ public static void main(String[] args) { try { @@ -213,8 +204,8 @@ public static void main(String[] args) { } /** - * Calls starts, waits the stableDelay, then calls stable. Does not return until this node is - * stopped. + * Calls starts, waits the stableDelay, then calls stable. Does not return until + * this node is stopped. */ public void run() { synchronized (this) { diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSMetadata.java b/dslink-core/src/main/java/org/iot/dsa/node/DSMetadata.java index 7270fb64..f0eb492c 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSMetadata.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSMetadata.java @@ -11,19 +11,19 @@ public class DSMetadata { // Constants /////////////////////////////////////////////////////////////////////////// - public static final String BOOLEAN_RANGE = "$booleanRange"; - public static final String DESCRIPTION = "$description"; - public static final String DECIMAL_PLACES = "$decimalPlaces"; - public static final String DEFAULT = "$default"; - public static final String DISPLAY_NAME = "$displayName"; - public static final String EDITOR = "$editor"; - public static final String ENUM_RANGE = "$enumRange"; - public static final String NAME = "$name"; - public static final String MAX_VALUE = "$maxValue"; - public static final String MIN_VALUE = "$minValue"; - public static final String PLACEHOLDER = "$placeholder"; - public static final String TYPE = "$type"; - public static final String UNIT = "$unit"; + public static final String BOOLEAN_RANGE = "booleanRange"; + public static final String DESCRIPTION = "description"; + public static final String DECIMAL_PLACES = "decimalPlaces"; + public static final String DEFAULT = "default"; + public static final String DISPLAY_NAME = "displayName"; + public static final String EDITOR = "editor"; + public static final String ENUM_RANGE = "enumRange"; + public static final String NAME = "name"; + public static final String MAX_VALUE = "maxValue"; + public static final String MIN_VALUE = "minValue"; + public static final String PLACEHOLDER = "placeholder"; + public static final String TYPE = "type"; + public static final String UNIT = "unit"; //public static final String EDITOR_DATE = "date"; //public static final String EDITOR_DATE_RANGE = "daterange"; diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSStatus.java b/dslink-core/src/main/java/org/iot/dsa/node/DSStatus.java index 3c2c5151..30cb9fd6 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSStatus.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSStatus.java @@ -3,61 +3,36 @@ import java.util.concurrent.ConcurrentHashMap; /** - * Represent the health, quality or condition of an object. - * + * Represents the health, quality or condition of an object. A DSStatus instance is a + * can represent multiple conditions. *

- * - * There are two categories of status, good and bad. Bad status means values can not be trusted and - * should not be used in decision making / calculations. When performing aggregations / intervals, - * bad values should be ignored if good values are present. - * - * + * There are three categories of status: good, bad and uncertain. Bad status means values + * can not be trusted and should not be used in decision making / calculations. + * Uncertain means the value might be good, but is the result of an operational or + * configuration error such as out of range. When performing aggregations and rollups, + * bad and uncertain values should be ignored if any good values are present. *

- * - * Remote status means the status is being reported by a foreign system (outside of DSA). This - * should greatly help with troubleshooting. - * - * + * Remote status means the status is being reported by a foreign system (outside of DSA). + * This should help with troubleshooting. + *

+ * The status values: *

- * - * Only the highest priority status should be assigned to an object. - * - * The status values in order from lowest to highest priority: - * *

* * @author Aaron Hansen @@ -73,116 +48,105 @@ public class DSStatus extends DSValue implements DSIStatus, DSIStorable { private static ConcurrentHashMap stringCache = new ConcurrentHashMap(); - public static final DSStatus NULL = new DSStatus(0xFFFF); + private static final int GOOD_MASK = 0x000000FF; + private static final int UNCERTAIN_MASK = 0x0000FF00; + private static final int BAD_MASK = 0x00FF0000; /** - * Good, no other status applies. + * Good, no other status applies. Always implied when not present. */ public static final int OK = 0; //"ok" /** * Good, the value is overridden within DSA. */ - public static final int OK_OVERRIDE = 0x00000001; //"override" + public static final int OVERRIDE = 0x00000001; //"override" /** - * Good, the value is overridden outside of DSA. + * Good, the remote system is reporting the value is overridden. */ - public static final int OK_REMOTE_OVERRIDE = 0x00000002; //"remoteOverride" + public static final int REMOTE_OVERRIDE = 0x00000002; //"remoteOverride" /** - * Bad, the value hasn't updated in a reasonable amount of time (user configurable on a per - * point basis) within DSA. + * Uncertain, the value hasn't updated in a reasonable amount of time (usually + * configurable) within DSA. */ public static final int STALE = 0x00000100; //"stale" /** - * Bad, communications are down within DSA. + * Uncertain, the remote system is reporting the value is stale. */ - public static final int DOWN = 0x00000200; //"down" + public static final int REMOTE_STALE = 0x00000200; //"remoteStale" /** - * Bad, an operational error (exception) has occurred within DSA. + * Uncertain, an operational or configuration error has occurred within DSA. */ public static final int FAULT = 0x00000400; //"fault" /** - * Bad, a configuration error has been indentified within DSA. + * Uncertain, the remote system is reporting a fault. */ - public static final int CONFIG_FAULT = 0x00000800; //"configFault" + public static final int REMOTE_FAULT = 0x00000800; //"remoteFault" /** - * Bad, the object has been disabled within DSA. + * Bad, a communication failure has occurred in DSA. */ - public static final int DISABLED = 0x00001000; //"disabled" + public static final int DOWN = 0x00010000; //"down" /** - * Bad, the status is unknown within DSA, typically the initial state at boot. - */ - public static final int UNKNOWN = 0x00002000; //"unknown" - - /** - * Bad, a stale value is being reported by the foreign system. - */ - public static final int REMOTE_STALE = 0x00010000; //"remoteStale" - - /** - * Bad, down communications are being reported by the foreign system. + * Bad, the remote system is reporting down. */ public static final int REMOTE_DOWN = 0x00020000; //"remoteDown" /** - * Bad, an operational error is being reported by the foreign system. + * Bad, the object has been disabled within DSA. */ - public static final int REMOTE_FAULT = 0x00040000; //"remoteFault" + public static final int DISABLED = 0x00040000; //"disabled" /** - * Bad, a configuration error is being reported by the foreign system. + * Bad, the remote system is reporting disabled. */ - public static final int REMOTE_CONFIG_FAULT = 0x00080000; //"remoteConfigFault" + public static final int REMOTE_DISABLED = 0x00080000; //"remoteDisabled" /** - * Bad, the foreign system is reporting the object is disabled. + * Bad, the status is unknown within DSA. This will typically be reported for + * invalid paths. */ - public static final int REMOTE_DISABLED = 0x00100000; //"remoteDisabled" + public static final int UNKNOWN = 0x00100000; //"unknown" /** - * Bad, the foreign system is reporting the status is unknown. + * Bad, the remote system is reporting unknown. */ public static final int REMOTE_UNKNOWN = 0x00200000; //"remoteUnknown" //The string for each unique status. public static final String OK_STR = "ok"; - public static final String OK_OVERRIDE_STR = "override"; - public static final String OK_REMOTE_OVERRIDE_STR = "remoteOverride"; + public static final String OVERRIDE_STR = "override"; public static final String STALE_STR = "stale"; public static final String DOWN_STR = "down"; - public static final String CONFIG_FAULT_STR = "configFault"; public static final String FAULT_STR = "fault"; public static final String DISABLED_STR = "disabled"; public static final String UNKNOWN_STR = "unknown"; + public static final String REMOTE_OVERRIDE_STR = "remoteOverride"; public static final String REMOTE_STALE_STR = "remoteStale"; - public static final String REMOTE_DOWN_STR = "remoteDown"; public static final String REMOTE_FAULT_STR = "remoteFault"; - public static final String REMOTE_CONFIG_FAULT_STR = "remoteConfigFault"; + public static final String REMOTE_DOWN_STR = "remoteDown"; public static final String REMOTE_DISABLED_STR = "remoteDisabled"; public static final String REMOTE_UNKNOWN_STR = "remoteUnknown"; //The instance for each unique status. public static final DSStatus ok = new DSStatus(0); - public static final DSStatus okOverride = valueOf(OK_OVERRIDE); - public static final DSStatus okRemoteOverride = valueOf(OK_REMOTE_OVERRIDE); + public static final DSStatus override = valueOf(OVERRIDE); + public static final DSStatus remoteOverride = valueOf(REMOTE_OVERRIDE); public static final DSStatus stale = valueOf(STALE); - public static final DSStatus down = valueOf(DOWN); - public static final DSStatus fault = valueOf(FAULT); - public static final DSStatus configFault = valueOf(CONFIG_FAULT); - public static final DSStatus disabled = valueOf(DISABLED); - public static final DSStatus unknown = valueOf(UNKNOWN); public static final DSStatus remoteStale = valueOf(REMOTE_STALE); - public static final DSStatus remoteDown = valueOf(REMOTE_DOWN); + public static final DSStatus fault = valueOf(FAULT); public static final DSStatus remoteFault = valueOf(REMOTE_FAULT); - public static final DSStatus remoteConfigFault = valueOf(REMOTE_CONFIG_FAULT); + public static final DSStatus down = valueOf(DOWN); + public static final DSStatus remoteDown = valueOf(REMOTE_DOWN); + public static final DSStatus disabled = valueOf(DISABLED); public static final DSStatus remoteDisabled = valueOf(REMOTE_DISABLED); + public static final DSStatus unknown = valueOf(UNKNOWN); public static final DSStatus remoteUnknown = valueOf(REMOTE_UNKNOWN); /////////////////////////////////////////////////////////////////////////// @@ -220,7 +184,7 @@ public boolean equals(Object obj) { * * @throws IllegalArgumentException If the string is unknown. */ - static Integer getBit(String s) { + private static Integer getBit(String s) { String orig = s; s = s.trim(); Integer i = stringCache.get(s); @@ -237,7 +201,7 @@ static Integer getBit(String s) { /** * The bitset representing the all the set qualities. */ - int getBits() { + public int getBits() { return bits; } @@ -258,14 +222,7 @@ public int hashCode() { * If any of the bad flags are set, or is null. */ public boolean isBad() { - return (bits & 0x11111100) != 0; - } - - /** - * True if the associate bit is set. - */ - public boolean isConfigFault() { - return (CONFIG_FAULT & bits) != 0; + return (bits & BAD_MASK) != 0; } /** @@ -301,12 +258,15 @@ public boolean isFault() { * If true, any associate object / value can be trusted. */ public boolean isGood() { - return !isBad(); + if (bits == 0) { + return true; + } + return (bits & GOOD_MASK) != 0; } @Override public boolean isNull() { - return this == NULL; + return false; } public boolean isOk() { @@ -317,11 +277,7 @@ public boolean isOk() { * True if the associate bit is set. */ public boolean isOverride() { - return (OK_OVERRIDE & bits) != 0; - } - - public boolean isRemoteConfigFault() { - return (REMOTE_CONFIG_FAULT & bits) != 0; + return (OVERRIDE & bits) != 0; } public boolean isRemoteDisabled() { @@ -337,7 +293,7 @@ public boolean isRemoteFault() { } public boolean isRemoteOverride() { - return (OK_REMOTE_OVERRIDE & bits) != 0; + return (REMOTE_OVERRIDE & bits) != 0; } public boolean isRemoteStale() { @@ -352,6 +308,13 @@ public boolean isStale() { return (STALE & bits) != 0; } + /** + * If true, any associate object / value might be bad. + */ + public boolean isUncertain() { + return (bits & UNCERTAIN_MASK) != 0; + } + public boolean isUnknown() { return (UNKNOWN & bits) != 0; } @@ -381,10 +344,10 @@ public String toString() { } StringBuilder buf = new StringBuilder(); if (isOverride()) { - append(buf, OK_OVERRIDE_STR); + append(buf, OVERRIDE_STR); } if (isRemoteOverride()) { - append(buf, OK_REMOTE_OVERRIDE_STR); + append(buf, REMOTE_OVERRIDE_STR); } if (isStale()) { append(buf, STALE_STR); @@ -395,9 +358,6 @@ public String toString() { if (isFault()) { append(buf, FAULT_STR); } - if (isConfigFault()) { - append(buf, CONFIG_FAULT_STR); - } if (isDisabled()) { append(buf, DISABLED_STR); } @@ -413,9 +373,6 @@ public String toString() { if (isRemoteFault()) { append(buf, REMOTE_FAULT_STR); } - if (isRemoteConfigFault()) { - append(buf, REMOTE_CONFIG_FAULT_STR); - } if (isRemoteDisabled()) { append(buf, REMOTE_DISABLED_STR); } @@ -433,15 +390,26 @@ public DSStatus toStatus() { @Override public DSStatus valueOf(DSElement element) { if ((element == null) || element.isNull()) { - return NULL; + return ok; } return valueOf(element.toString()); } - static DSStatus valueOf(int bits) { - if (bits == NULL.bits) { - return NULL; + /** + * Returns a status representing all of the given bits. + */ + public static DSStatus valueOf(int... bits) { + int all = 0; + for (int bit : bits) { + all |= bit; } + return valueOf(bits); + } + + /** + * Returns a status representing the given bitset. + */ + public static DSStatus valueOf(int bits) { if (bits == 0) { return ok; } @@ -454,6 +422,11 @@ static DSStatus valueOf(int bits) { return ret; } + /** + * Returns a status representing all of the status strings (comma separated). + * + * @param string CSV of status names. + */ public static DSStatus valueOf(String string) { return valueOf(string.split(",")); } @@ -479,23 +452,20 @@ static DSStatus valueOf(String... strings) { /////////////////////////////////////////////////////////////////////////// static { - DSRegistry.registerDecoder(DSStatus.class, NULL); - stringCache.put("null", NULL.bits); + DSRegistry.registerDecoder(DSStatus.class, ok); stringCache.put("disconnected", DOWN); //DSAv1 stringCache.put(OK_STR, OK); - stringCache.put(OK_OVERRIDE_STR, OK_OVERRIDE); - stringCache.put(OK_REMOTE_OVERRIDE_STR, OK_REMOTE_OVERRIDE); + stringCache.put(OVERRIDE_STR, OVERRIDE); + stringCache.put(REMOTE_OVERRIDE_STR, REMOTE_OVERRIDE); stringCache.put(STALE_STR, STALE); - stringCache.put(DOWN_STR, DOWN); - stringCache.put(FAULT_STR, FAULT); - stringCache.put(CONFIG_FAULT_STR, CONFIG_FAULT); - stringCache.put(DISABLED_STR, DISABLED); - stringCache.put(UNKNOWN_STR, UNKNOWN); stringCache.put(REMOTE_STALE_STR, REMOTE_STALE); - stringCache.put(REMOTE_DOWN_STR, REMOTE_DOWN); + stringCache.put(FAULT_STR, FAULT); stringCache.put(REMOTE_FAULT_STR, REMOTE_FAULT); - stringCache.put(REMOTE_CONFIG_FAULT_STR, REMOTE_CONFIG_FAULT); + stringCache.put(DOWN_STR, DOWN); + stringCache.put(REMOTE_DOWN_STR, REMOTE_DOWN); + stringCache.put(DISABLED_STR, DISABLED); stringCache.put(REMOTE_DISABLED_STR, REMOTE_DISABLED); + stringCache.put(UNKNOWN_STR, UNKNOWN); stringCache.put(REMOTE_UNKNOWN_STR, REMOTE_UNKNOWN); } diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 56c5ff13..563a2546 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -1,7 +1,7 @@ { "name": "dslink-java-v2-test", "version": "1.0.0", - "dsa-version": "2.0", + "dsa-version": "1.0", "description": "Testing Link", "main": "bin/dslink-java-v2-test", "configs": { @@ -12,7 +12,7 @@ "log": { "desc": "all, trace, debug, fine, warn, info, error, admin, fatal, none", "type": "enum", - "value": "debug" + "value": "info" }, "token": { "desc": "Authentication token for the broker.", diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 6806be38..7a3265ee 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ddb5cd5b..ea720f98 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sun Aug 13 14:38:55 PDT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip diff --git a/gradlew b/gradlew index 4453ccea..cccdd3d5 100644 --- a/gradlew +++ b/gradlew @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -155,7 +155,7 @@ if $cygwin ; then fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " }