class EtcdClient {

	private static final Logger LOG = LoggerFactory.getLogger(EtcdClient.class);

	private final Client client;

	private final ConcurrentHashMap<String, Watch.Watcher> watchCache = new ConcurrentHashMap<>();

	public EtcdClient(final Client client) {
		this.client = client;
	}

	/**
	 * close client.
	 */
	public void close() {
		this.client.close();
	}

	/**
	 * get node value.
	 * @param key node name
	 * @return string
	 */
	public String get(final String key) {
		List<KeyValue> keyValues = null;
		try {
			keyValues = client.getKVClient().get(bytesOf(key)).get().getKvs();
		}
		catch (InterruptedException | ExecutionException e) {
			LOG.error(e.getMessage(), e);
		}

		if (CollUtil.isEmpty(keyValues)) {
			return null;
		}

		return keyValues.iterator().next().getValue().toString(UTF_8);
	}

	/**
	 * put a key-value pair into etcd.
	 * @param key node name
	 * @param value node value
	 * @return
	 */
	public KeyValue put(final String key, final String value) {
		KeyValue prevKv = null;
		ByteSequence keyByteSequence = bytesOf(key);
		ByteSequence valueByteSequence = bytesOf(value);
		try {
			prevKv = client.getKVClient().put(keyByteSequence, valueByteSequence).get().getPrevKv();
		}
		catch (InterruptedException | ExecutionException e) {
			LOG.error(e.getMessage(), e);
		}
		return prevKv;
	}

	/**
	 * get node sub nodes.
	 * @param prefix node prefix.
	 * @param separator separator char
	 * @return sub nodes
	 * @throws ExecutionException the exception
	 * @throws InterruptedException the exception
	 */
	public List<String> getChildrenKeys(final String prefix, final String separator)
			throws ExecutionException, InterruptedException {<FILL_FUNCTION_BODY>}

	private String getSubNodeKeyName(final String prefix, final String fullPath, final String separator) {
		if (prefix.length() > fullPath.length()) {
			return null;
		}
		String pathWithoutPrefix = fullPath.substring(prefix.length());
		return pathWithoutPrefix.contains(separator) ? pathWithoutPrefix.substring(1) : pathWithoutPrefix;
	}

	/**
	 * subscribe data change.
	 * @param key node name
	 * @param updateHandler node value handler of update
	 * @param deleteHandler node value handler of delete
	 */
	public void watchDataChange(final String key, final BiConsumer<String, String> updateHandler,
			final Consumer<String> deleteHandler) {
		Watch.Listener listener = watch(updateHandler, deleteHandler);
		Watch.Watcher watch = client.getWatchClient().watch(bytesOf(key), listener);
		watchCache.put(key, watch);
	}

	/**
	 * subscribe sub node change.
	 * @param key param node name.
	 * @param updateHandler sub node handler of update
	 * @param deleteHandler sub node delete of delete
	 */
	public void watchChildChange(final String key, final BiConsumer<String, String> updateHandler,
			final Consumer<String> deleteHandler) {
		Watch.Listener listener = watch(updateHandler, deleteHandler);
		WatchOption option = WatchOption.newBuilder().withPrefix(bytesOf(key)).build();
		Watch.Watcher watch = client.getWatchClient().watch(bytesOf(key), option, listener);
		watchCache.put(key, watch);
	}

	/**
	 * bytesOf string.
	 * @param val val.
	 * @return bytes val.
	 */
	public ByteSequence bytesOf(final String val) {
		return ByteSequence.from(val, UTF_8);
	}

	private Watch.Listener watch(final BiConsumer<String, String> updateHandler, final Consumer<String> deleteHandler) {
		return Watch.listener(response -> {
			for (WatchEvent event : response.getEvents()) {
				String path = event.getKeyValue().getKey().toString(UTF_8);
				String value = event.getKeyValue().getValue().toString(UTF_8);
				switch (event.getEventType()) {
					case PUT:
						updateHandler.accept(path, value);
						continue;
					case DELETE:
						deleteHandler.accept(path);
						continue;
					default:
				}
			}
		});
	}

	/**
	 * cancel subscribe.
	 * @param key node name
	 */
	public void watchClose(final String key) {
		if (watchCache.containsKey(key)) {
			watchCache.get(key).close();
			watchCache.remove(key);
		}
	}

}

		ByteSequence prefixByteSequence = bytesOf(prefix);
		GetOption getOption = GetOption.newBuilder()
			.withPrefix(prefixByteSequence)
			.withSortField(GetOption.SortTarget.KEY)
			.withSortOrder(GetOption.SortOrder.ASCEND)
			.build();

		List<KeyValue> keyValues = client.getKVClient().get(prefixByteSequence, getOption).get().getKvs();

		return keyValues.stream()
			.map(e -> getSubNodeKeyName(prefix, e.getKey().toString(UTF_8), separator))
			.distinct()
			.filter(e -> Objects.nonNull(e))
			.collect(Collectors.toList());
	
<no_super_class>