package dmg.cells.nucleus; import com.google.common.collect.Lists; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkArgument; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; /** * The CellPath is an abstraction of the path a CellMessage is * assumed to travel. The path consists of a defined sequence of * cell hops and a current position. The last hop which might * as well be the only one, is called the FinalDestination. * At any point a new Cell Hop can be added in two ways : * <ul> * <li>At the end of the sequence. The added Cell becomes the * new FinalDestination. * <li>Insert the new Cell behind the current position. The new * Hop becomes the next hop. * </ul> * The string representation of a cell path can have the format: * <pre> * path : <addr1>[:<<addr2>[...]] * addr : <cellName> | <cellName@domainName> | <cellName@local> * </pre> * * @author Patrick Fuhrmann * @version 0.1, 15 Feb 1998 */ public final class CellPath implements Cloneable, Serializable { private static final long serialVersionUID = -4922955783102747577L; private final List<CellAddressCore> _list; private int _position; private CellPath(int position, List<CellAddressCore> list) { checkArgument(position >= 0 && position <= list.size()); _position = position; _list = list; } protected CellPath() { this(0, new ArrayList<>()); } public CellPath(String path) { this(0, streamOfPath(path).collect(toList())); } public CellPath(CellAddressCore... address) { this(0, Lists.newArrayList(address)); } public CellPath(CellPath path, CellAddressCore... addresses) { this(0, Stream.concat(path._list.stream(), Stream.of(addresses)).collect(toList())); } public CellPath(String cellName, String domainName) { this(new CellAddressCore(cellName, domainName)); } public synchronized int hops() { return _list.size(); } public synchronized void add(CellAddressCore core) { _list.add(core); } public synchronized void add(CellPath addr) { _list.addAll(addr._list); } /** * Adds a cell path <path> to the end of the current path. * * @param path The added cell travel path. */ public synchronized void add(String path) { streamOfPath(path).forEachOrdered(this::add); } @Override public synchronized CellPath clone() { return new CellPath(_position, new ArrayList<>(_list)); } /** * Adds a cell path <path> at the current position. */ public synchronized void insert(CellPath path) { _list.addAll(_position, path._list); } public synchronized void insert(CellAddressCore address) { _list.add(_position, address); } /** * Increment the current cell position by one. * * @return true if the path was not at the last position before the call, false otherwise. */ public synchronized boolean next() { int size = _list.size(); if (_position < size) { _position++; } return _position < size; } /** * Returns a new path that is the reverse of this path. * * The new path will have been collapsed such that only cells are * addressed. Domain addresses will have been stripped, except if * followed by a local (not fully qualified) address. */ public synchronized CellPath revert() { CellPath path = new CellPath(); Iterator<CellAddressCore> iterator = Lists.reverse(_list).iterator(); if (iterator.hasNext()) { CellAddressCore address = iterator.next(); while (iterator.hasNext()) { CellAddressCore next = iterator.next(); if (!address.isDomainAddress() || next.isLocalAddress()) { path.add(address); } address = next; } path.add(address); } return path; } public synchronized boolean isFinalDestination() { return _position >= _list.size(); } public synchronized boolean isFirstDestination() { return _position == 0; } public synchronized CellAddressCore getCurrent() { return (_position >= _list.size()) ? null : _list.get(_position); } public synchronized CellAddressCore getSourceAddress() { return _list.get(0); } public synchronized CellAddressCore getDestinationAddress() { return _list.get(_list.size() - 1); } synchronized void replaceCurrent(CellAddressCore core) { if (_position < _list.size()) { _list.set(_position, core); } } public String getCellName() { CellAddressCore core = getCurrent(); return core == null ? null : core.getCellName(); } public String getCellDomainName() { CellAddressCore core = getCurrent(); return core == null ? null : core.getCellDomainName(); } public synchronized String toSmallString() { int size = _list.size(); if (size == 0) { return "[empty]"; } if ((_position >= size) || (_position < 0)) { return "[INVALID]"; } CellAddressCore core = _list.get(_position); if (size == 1) { return '[' + core.toString() + ']'; } if (_position == 0) { return '[' + core.toString() + ":...(" + (size - 1) + ")...]"; } if (_position == (size - 1)) { return "[...(" + (size - 1) + ")...:" + core + ']'; } return "[...(" + _position + ")...:" + core + "...(" + (size - _position - 1) + ")...]"; } /** * Returns the cell path as a colon separated list of addresses. This is the same format * accepted by the string constructor of CellPath. */ public String toAddressString() { return _list.stream().map(CellAddressCore::toString).collect(joining(":")); } public List<CellAddressCore> getAddresses() { return Collections.unmodifiableList(_list); } @Override public String toString() { return toFullString(); } public synchronized String toFullString() { int size = _list.size(); if (size == 0) { return "[empty]"; } int position = _position; if (position > size || position < 0) { return "[INVALID]"; } StringBuilder sb = new StringBuilder(size * 16); sb.append('['); CellAddressCore address; int i = 0; if (position < size) { for (; i < position; i++) { address = _list.get(i); sb.append(address.getCellName()).append('@').append(address.getCellDomainName()); sb.append(':'); } sb.append('>'); } address = _list.get(i); sb.append(address.getCellName()).append('@').append(address.getCellDomainName()); for (i++; i < size; i++) { sb.append(':'); address = _list.get(i); sb.append(address.getCellName()).append('@').append(address.getCellDomainName()); } sb.append(']'); return sb.toString(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof CellPath)) { return false; } List<CellAddressCore> other; synchronized (obj) { other = new ArrayList<>(((CellPath) obj)._list); } synchronized (this) { return _list.equals(other); } } @Override public synchronized int hashCode() { /* Beware that equals only takes the list of addresses into account. */ return _list.hashCode(); } private static Stream<CellAddressCore> streamOfPath(String path) { checkArgument(!path.isEmpty()); return Stream.of(path.split(":")).map(CellAddressCore::new); } public boolean contains(CellAddressCore address) { return _list.contains(address); } /** * Writes CellPath to a data output stream. * * This is the raw encoding used by tunnels since release 3.0. */ public void writeTo(DataOutput out) throws IOException { out.writeInt(_list.size()); out.writeInt(_position); for (CellAddressCore cellAddressCore : _list) { out.writeUTF(cellAddressCore.getCellName()); out.writeUTF(cellAddressCore.getCellDomainName()); } } /** * Reads CellPath from a data input stream. * * This is the raw encoding used by tunnels since release 3.0. */ public static CellPath createFrom(DataInput in) throws IOException { int len = in.readInt(); int position = in.readInt(); ArrayList<CellAddressCore> list = new ArrayList<>(len); for (int i = 0; i < len; i++) { list.add(new CellAddressCore(in.readUTF(), in.readUTF().intern())); } return new CellPath(position, list); } }