package net.ion.craken.node.crud.tree;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.ion.craken.loaders.EntryKey;
import net.ion.craken.node.crud.tree.impl.FqnComparator;
import net.ion.craken.node.crud.tree.impl.PropertyValue;
import net.ion.craken.node.crud.tree.impl.PropertyValue.VType;
import net.ion.craken.node.crud.tree.impl.TreeNodeKey;
import net.ion.craken.node.crud.tree.impl.TreeNodeKey.Type;
import net.ion.framework.parse.gson.JsonPrimitive;
import net.ion.framework.util.MapUtil;
import net.ion.framework.util.ObjectUtil;
import net.ion.radon.util.uriparser.URIPattern;
import net.ion.radon.util.uriparser.URIResolveResult;
import net.ion.radon.util.uriparser.URIResolver;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.infinispan.commons.marshall.AbstractExternalizer;
import org.infinispan.commons.marshall.SerializeWith;
import org.infinispan.commons.util.Util;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
// @Immutable
@SerializeWith(Fqn.Externalizer.class)
public class Fqn implements Comparable<Fqn>, Serializable, PropertyValue.ReplaceValue<String> {
private static final long serialVersionUID = 7459897811324670392L;
public static final String SEPARATOR = "/";
private final String[] elements;
private transient int hash_code = 0;
public static final Fqn ROOT = new Fqn();
public static final Fqn TRANSACTIONS = Fqn.fromString("/__transactions");
protected String stringRepresentation;
private static final String[] EMPTY_ARRAY = new String[0];
private TreeNodeKey dataKey;
private TreeNodeKey struKey;
private Fqn(String... elements) {
this.elements = elements;
initKey();
}
private Fqn(List<String> names) {
elements = (names != null) ? names.toArray(new String[0]) : EMPTY_ARRAY;
initKey() ;
}
private Fqn(Fqn base, Object... relative) {
elements = new String[base.elements.length + relative.length];
System.arraycopy(base.elements, 0, elements, 0, base.elements.length);
System.arraycopy(relative, 0, elements, base.elements.length, relative.length);
initKey() ;
}
private void initKey() {
this.dataKey = new TreeNodeKey(this, Type.DATA) ;
this.struKey = new TreeNodeKey(this, Type.STRUCTURE) ;
}
// ----------------- END: Private constructors for use by factory methods only. ----------------------
public TreeNodeKey dataKey(){
return dataKey ;
}
public TreeNodeKey struKey(){
return struKey ;
}
@SuppressWarnings("unchecked")
public static Fqn fromList(List<String> names) {
return new Fqn(names);
}
public static Fqn fromElements(String... elements) {
String[] copy = new String[elements.length];
System.arraycopy(elements, 0, copy, 0, elements.length);
return new Fqn(copy);
}
public static Fqn fromRelativeFqn(Fqn base, Fqn relative) {
return new Fqn(base, relative.elements);
}
public static Fqn fromRelativeList(Fqn base, List<?> relativeElements) {
return new Fqn(base, relativeElements.toArray());
}
public static Fqn fromRelativeElements(Fqn base, Object... relativeElements) {
return new Fqn(base, relativeElements);
}
public static Fqn fromString(String stringRepresentation) {
if (stringRepresentation == null || stringRepresentation.equals(SEPARATOR) || stringRepresentation.length() == 0)
return root();
String toMatch = stringRepresentation.startsWith(SEPARATOR) ? stringRepresentation.substring(1) : stringRepresentation;
String[] el = toMatch.split(SEPARATOR);
// return new Fqn(el) ;
return new Fqn(Iterables.toArray(Splitter.on(SEPARATOR).trimResults().omitEmptyStrings().split(toMatch), String.class));
}
public Fqn getAncestor(int generation) {
if (generation == 0)
return root();
return getSubFqn(0, generation);
}
public Fqn getSubFqn(int startIndex, int endIndex) {
if (endIndex < startIndex)
throw new IllegalArgumentException("End index cannot be less than the start index!");
int len = endIndex - startIndex;
String[] el = new String[len];
System.arraycopy(elements, startIndex, el, 0, len);
return new Fqn(el);
}
public int size() {
return elements.length;
}
public Object get(int n) {
if (n < 0) return elements[size() + n] ;
return elements[n];
}
public JsonPrimitive toJson() {
return new JsonPrimitive(toString());
}
public Object getLastElement() {
if (isRoot())
return null;
return elements[elements.length - 1];
}
public boolean hasElement(Object element) {
return indexOf(element) != -1;
}
private int indexOf(Object element) {
if (element == null) {
for (int i = 0; i < elements.length; i++) {
if (elements[i] == null)
return i;
}
} else {
for (int i = 0; i < elements.length; i++) {
if (element.equals(elements[i]))
return i;
}
}
return -1;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Fqn)) {
return false;
}
Fqn other = (Fqn) obj;
if (elements.length != other.elements.length)
return false;
for (int i = elements.length - 1; i >= 0; i--) {
if (!Util.safeEquals(elements[i], other.elements[i]))
return false;
}
return true;
}
@Override
public int hashCode() {
if (hash_code == 0) {
hash_code = calculateHashCode();
}
return hash_code;
}
@Override
public String toString() {
if (stringRepresentation == null) {
stringRepresentation = getStringRepresentation(elements);
}
return stringRepresentation;
}
public boolean isChildOf(Fqn parentFqn) {
return parentFqn.elements.length != elements.length && isChildOrEquals(parentFqn);
}
/**
* Returns true if this Fqn is a <i>direct</i> child of a given Fqn.
*
* @param parentFqn
* parentFqn to compare with
* @return true if this is a direct child, false otherwise.
*/
public boolean isDirectChildOf(Fqn parentFqn) {
return elements.length == parentFqn.elements.length + 1 && isChildOf(parentFqn);
}
public boolean isChildOrEquals(Fqn parentFqn) {
Object[] parentEl = parentFqn.elements;
if (parentEl.length > elements.length) {
return false;
}
for (int i = parentEl.length - 1; i >= 0; i--) {
if (!Util.safeEquals(parentEl[i], elements[i]))
return false;
}
return true;
}
protected int calculateHashCode() {
int hashCode = 19;
for (Object o : elements)
hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode());
if (hashCode == 0)
hashCode = 0xDEADBEEF; // degenerate case
return hashCode;
}
protected String getStringRepresentation(Object[] elements) {
StringBuilder builder = new StringBuilder();
for (Object e : elements) {
// incase user element 'e' does not implement equals() properly, don't rely on their implementation.
if (!SEPARATOR.equals(e) && !"".equals(e)) {
builder.append(SEPARATOR);
builder.append(e);
}
}
return builder.length() == 0 ? SEPARATOR : builder.toString();
}
public Fqn getParent() {
switch (elements.length) {
case 0:
case 1:
return root();
default:
return getSubFqn(0, elements.length - 1);
}
}
public static Fqn root() { // declared final so compilers can optimise and in-line.
return ROOT;
}
public boolean isRoot() {
return elements.length == 0;
}
public String getLastElementAsString() {
if (isRoot()) {
return SEPARATOR;
} else {
Object last = getLastElement();
if (last instanceof String)
return (String) last;
else
return String.valueOf(getLastElement());
}
}
public List<String> peekElements() {
return Arrays.asList(elements);
}
@Override
public int compareTo(Fqn fqn) {
return FqnComparator.INSTANCE.compare(this, fqn);
}
public Fqn replaceAncestor(Fqn oldAncestor, Fqn newAncestor) {
if (!isChildOf(oldAncestor))
throw new IllegalArgumentException("Old ancestor must be an ancestor of the current Fqn!");
Fqn subFqn = this.getSubFqn(oldAncestor.size(), size());
return Fqn.fromRelativeFqn(newAncestor, subFqn);
}
public static class Externalizer extends AbstractExternalizer<Fqn> {
@Override
public void writeObject(ObjectOutput output, Fqn fqn) throws IOException {
output.writeInt(fqn.elements.length);
for (String element : fqn.elements)
output.writeUTF(element);
}
@Override
public Fqn readObject(ObjectInput input) throws IOException, ClassNotFoundException {
int size = input.readInt();
String[] elements = new String[size];
for (int i = 0; i < size; i++)
elements[i] = input.readUTF();
return new Fqn(elements);
}
@Override
public Set<Class<? extends Fqn>> getTypeClasses() {
return Util.<Class<? extends Fqn>> asSet(Fqn.class);
}
}
public String name() {
return ObjectUtil.toString(getLastElement());
}
public String startWith() {
return isRoot() ? "/*" : toString() + "/*";
}
public Query childrenQuery() {
BooleanQuery result = new BooleanQuery();
// result.add(new TermQuery(new Term(EntryKey.PARENT, "/__transactions")), Occur.MUST_NOT);
result.add(new WildcardQuery(new Term(EntryKey.PARENT, this.startWith())), Occur.SHOULD);
result.add(new TermQuery(new Term(EntryKey.PARENT, this.toString())), Occur.SHOULD);
return result;
// return new WildcardQuery(new Term(DocEntry.PARENT, this.startWith())) ;
}
@Override
public String replaceValue() {
return toString();
}
@Override
public VType vtype() {
return VType.STR;
}
public boolean isPattern(String fqnPattern) {
return new URIPattern(fqnPattern).match(this.toString());
}
public boolean isPattern(URIPattern uriPattern) {
return uriPattern.match(this.toString());
}
public Map<String, String> resolve(String fqnPattern){
URIResolveResult resolver = new URIResolver(toString()).resolve(new URIPattern(fqnPattern));
Map<String, String> result = MapUtil.newMap() ;
for(String name : resolver.names()){
result.put(name, ObjectUtil.toString(resolver.get(name))) ;
}
return result ;
}
}