package org.basex.query.item; import static org.basex.query.util.Err.*; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.expr.Expr; import org.basex.query.item.map.Map; import org.basex.query.iter.ItemCache; import org.basex.query.util.Err; import org.basex.util.InputInfo; /** * Stores a sequence type definition. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class SeqType { /** Number of occurrences (cardinality). */ public enum Occ { /** Zero. */ Z(0, 0, ""), /** Zero or one. */ ZO(0, 1, "?"), /** Exactly one. */ O(1, 1, ""), /** One or more. */ OM(1, Integer.MAX_VALUE, "+"), /** Zero or more. */ ZM(0, Integer.MAX_VALUE, "*"); /** String representation. */ private final String str; /** Minimal number of occurrences. */ public final int min; /** Maximal number of occurrences. */ public final int max; /** * Constructor. * @param mn minimal number of occurrences * @param mx maximal number of occurrences * @param s string representation */ Occ(final int mn, final int mx, final String s) { min = mn; max = mx; str = s; } /** * Checks if the specified occurrence indicator is an instance of the * current occurrence indicator. * @param o occurrence indicator to check * @return result of check */ public boolean instance(final Occ o) { return min >= o.min && max <= o.max; } /** * Checks if the given cardinality is supported by this type. * @param c cardinality * @return result of check */ public boolean check(final long c) { return min <= c && c <= max; } @Override public String toString() { return str; } } /** Zero items. */ public static final SeqType EMP = new SeqType(AtomType.EMP, Occ.Z); /** Single item. */ public static final SeqType ITEM = AtomType.ITEM.seqType(); /** Zero or one item. */ public static final SeqType ITEM_ZO = new SeqType(AtomType.ITEM, Occ.ZO); /** Zero or more items. */ public static final SeqType ITEM_ZM = new SeqType(AtomType.ITEM, Occ.ZM); /** One or more items. */ public static final SeqType ITEM_OM = new SeqType(AtomType.ITEM, Occ.OM); /** Zero or one xs:anyAtomicType. */ public static final SeqType AAT = AtomType.AAT.seqType(); /** Zero or one xs:anyAtomicType. */ public static final SeqType AAT_ZO = new SeqType(AtomType.AAT, Occ.ZO); /** Zero or more xs:anyAtomicType. */ public static final SeqType AAT_ZM = new SeqType(AtomType.AAT, Occ.ZM); /** Single xs:boolean. */ public static final SeqType BLN = AtomType.BLN.seqType(); /** Zero or one xs:boolean. */ public static final SeqType BLN_ZO = new SeqType(AtomType.BLN, Occ.ZO); /** Double number. */ public static final SeqType DBL = AtomType.DBL.seqType(); /** Double number. */ public static final SeqType DBL_ZM = new SeqType(AtomType.DBL, Occ.ZM); /** Zero or one double. */ public static final SeqType DBL_ZO = new SeqType(AtomType.DBL, Occ.ZO); /** Zero or one decimal number. */ public static final SeqType DEC_ZO = new SeqType(AtomType.DEC, Occ.ZO); /** Single number; for simplicity, numbers are summarized by this type. */ public static final SeqType ITR = AtomType.ITR.seqType(); /** Zero or one number. */ public static final SeqType ITR_ZO = new SeqType(AtomType.ITR, Occ.ZO); /** Zero or more numbers. */ public static final SeqType ITR_ZM = new SeqType(AtomType.ITR, Occ.ZM); /** One or more numbers. */ public static final SeqType ITR_OM = new SeqType(AtomType.ITR, Occ.OM); /** Single node. */ public static final SeqType NOD = NodeType.NOD.seqType(); /** Zero or one nodes. */ public static final SeqType NOD_ZO = new SeqType(NodeType.NOD, Occ.ZO); /** Zero or more nodes. */ public static final SeqType NOD_ZM = new SeqType(NodeType.NOD, Occ.ZM); /** One or more nodes. */ public static final SeqType NOD_OM = new SeqType(NodeType.NOD, Occ.OM); /** Single QName. */ public static final SeqType QNM = AtomType.QNM.seqType(); /** Zero or one QNames. */ public static final SeqType QNM_ZO = new SeqType(AtomType.QNM, Occ.ZO); /** Single URI. */ public static final SeqType URI = AtomType.URI.seqType(); /** Zero or one URIs. */ public static final SeqType URI_ZO = new SeqType(AtomType.URI, Occ.ZO); /** Zero or more URIs. */ public static final SeqType URI_ZM = new SeqType(AtomType.URI, Occ.ZM); /** Single string. */ public static final SeqType STR = AtomType.STR.seqType(); /** Zero or one strings. */ public static final SeqType STR_ZO = new SeqType(AtomType.STR, Occ.ZO); /** Zero or more strings. */ public static final SeqType STR_ZM = new SeqType(AtomType.STR, Occ.ZM); /** Zero or one NCName. */ public static final SeqType NCN_ZO = new SeqType(AtomType.NCN, Occ.ZO); /** Single date. */ public static final SeqType DAT = AtomType.DAT.seqType(); /** Zero or one date. */ public static final SeqType DAT_ZO = new SeqType(AtomType.DAT, Occ.ZO); /** One day-time-duration. */ public static final SeqType DTD = AtomType.DTD.seqType(); /** Zero or one day-time-duration. */ public static final SeqType DTD_ZO = new SeqType(AtomType.DTD, Occ.ZO); /** One date-time. */ public static final SeqType DTM = AtomType.DTM.seqType(); /** Zero or one date-time. */ public static final SeqType DTM_ZO = new SeqType(AtomType.DTM, Occ.ZO); /** One time. */ public static final SeqType TIM = AtomType.TIM.seqType(); /** Zero or one time. */ public static final SeqType TIM_ZO = new SeqType(AtomType.TIM, Occ.ZO); /** Zero or one duration. */ public static final SeqType DUR_ZO = new SeqType(AtomType.DUR, Occ.ZO); /** Single function. */ public static final SeqType FUN_O = FuncType.ANY_FUN.seqType(); /** Zero or more bytes. */ public static final SeqType BYT_ZM = new SeqType(AtomType.BYT, Occ.ZM); /** One document node. */ public static final SeqType DOC_O = NodeType.DOC.seqType(); /** Zero or one document node. */ public static final SeqType DOC_ZO = new SeqType(NodeType.DOC, Occ.ZO); /** Zero or more document node. */ public static final SeqType DOC_ZM = new SeqType(NodeType.DOC, Occ.ZM); /** One element node. */ public static final SeqType ELM = NodeType.ELM.seqType(); /** Zero or more element nodes. */ public static final SeqType ELM_ZM = new SeqType(NodeType.ELM, Occ.ZM); /** The general map type. */ public static final MapType ANY_MAP = new MapType(AtomType.AAT, ITEM_ZM); /** Single function. */ public static final SeqType MAP_ZM = new SeqType(ANY_MAP, Occ.ZM); /** Single function. */ public static final SeqType MAP_O = new SeqType( MapType.get(AtomType.AAT, ITEM_ZM)); /** One xs:hexBinary. */ public static final SeqType HEX = AtomType.HEX.seqType(); /** Single xs:base64Binary. */ public static final SeqType B64 = AtomType.B64.seqType(); /** Sequence type. */ public final Type type; /** Number of occurrences. */ public final Occ occ; /** Extended type info. */ private final QNm ext; /** * Private constructor. * @param t type * @param o occurrences */ private SeqType(final Type t, final Occ o) { this(t, o, null); } /** * Constructor. This one is only called by {@link Type#seqType} to create * unique sequence type instances. * @param t type */ SeqType(final Type t) { this(t, Occ.O); } /** * Private constructor. * @param t type * @param o occurrences * @param e extension */ private SeqType(final Type t, final Occ o, final QNm e) { type = t; occ = t == AtomType.EMP ? Occ.Z : t == AtomType.SEQ ? Occ.OM : o; ext = e; } /** * Returns a sequence type. * @param t type * @param o occurrences * @return sequence type */ public static SeqType get(final Type t, final Occ o) { return get(t, o, null); } /** * Returns a sequence type. * @param t type * @param o number of occurrences * @return sequence type */ public static SeqType get(final Type t, final long o) { return get(t, o == 0 ? Occ.Z : o == 1 ? Occ.O : o > 1 ? Occ.OM : Occ.ZM); } /** * Returns a sequence type. * @param t type * @param o occurrences * @param e extension * @return sequence type */ public static SeqType get(final Type t, final Occ o, final QNm e) { return o == Occ.O && e == null ? t.seqType() : new SeqType(t, o, e); } /** * Matches a value against this sequence type. * @param val value to be checked * @return result of check */ public boolean instance(final Value val) { final long size = val.size(); if(!occ.check(size)) return false; // the empty sequence has every type if(size == 0) return true; final MapType mt = type.isMap() ? (MapType) type : null; for(long i = 0; i < size; i++) { final Item it = val.itemAt(i); // maps don't have type information attached to them, you have to look... final Type ip = it.type; if(mt == null) { if(!(ip.instanceOf(type) && checkExt(it))) return false; } else { if(!(ip.isMap() && ((Map) it).hasType(mt))) return false; } if(i == 0 && val.homogenous()) break; } return true; } /** * Tries to promote the given item to this sequence type. * @param it item to promote * @param e producing expression * @param cast explicit cast flag * @param ctx query context * @param ii input info * @return promoted item * @throws QueryException query exception */ public Item cast(final Item it, final Expr e, final boolean cast, final QueryContext ctx, final InputInfo ii) throws QueryException { if(it == null) { if(occ == Occ.O) XPEMPTY.thrw(ii, e.description()); return null; } final boolean correct = cast ? it.type == type : instance(it, ii); return check(correct ? it : type.cast(it, ctx, ii), ii); } /** * Promotes the specified value. * @param val value to be cast * @param ctx query context * @param ii input info * @return resulting item * @throws QueryException query exception */ public Value promote(final Value val, final QueryContext ctx, final InputInfo ii) throws QueryException { final long size = val.size(); if(!occ.check(size)) Err.promote(ii, type, val); // empty sequence has all types if(size == 0) return val; final Item f = val.itemAt(0); // take shortcut if it's a single item if(size == 1) return check(instance(f, ii) ? f : type.cast(f, ctx, ii), ii); // only cache if absolutely necessary if(val.homogenous() && instance(f, ii) && checkExt(f)) return val; // no way around it... final ItemCache ic = new ItemCache((int) size); for(long i = 0; i < size; i++) { final Item n = val.itemAt(i); ic.add(check(instance(n, ii) ? n : type.cast(n, ctx, ii), ii)); } return ic.value(); } /** * Returns whether item has already correct type. * @param it input item * @param ii input info * @return result of check * @throws QueryException query exception */ private boolean instance(final Item it, final InputInfo ii) throws QueryException { final Type ip = it.type; final boolean ins = ip.instanceOf(type); if(!ins && !ip.isUntyped() && !ip.isFunction() && // implicit type promotions: // xs:float -> xs:double (ip != AtomType.FLT || type != AtomType.DBL) && // xs:anyUri -> xs:string (ip != AtomType.URI || type != AtomType.STR) && // xs:decimal -> xs:float/xs:double (type != AtomType.FLT && type != AtomType.DBL || !ip.instanceOf(AtomType.DEC))) Err.promote(ii, type, it); return ins; } /** * Combine two sequence types. * @param t second type * @return resulting type */ public SeqType intersect(final SeqType t) { final Type tp = type == t.type ? type : AtomType.ITEM; final Occ oc = occ == t.occ ? occ : zeroOrOne() && t.zeroOrOne() ? Occ.ZO : Occ.ZM; return new SeqType(tp, oc); } /** * Returns the number of occurrences, or {@code -1} if the number is unknown. * @return result of check */ public long occ() { return occ == Occ.Z ? 0 : occ == Occ.O ? 1 : -1; } /** * Tests if the type yields at most one item. * @return result of check */ public boolean zeroOrOne() { return occ.max <= 1; } /** * Tests if the type exactly one item. * @return result of check */ public boolean one() { return occ == Occ.O; } /** * Tests if the type may yield zero items. * @return result of check */ public boolean mayBeZero() { return occ.min == 0; } /** * Tests if the type is a single number. * @return result of check */ public boolean isNum() { return one() && type.isNumber(); } /** * Tests if the type may be numeric. * @return result of check */ public boolean mayBeNum() { return type.isNumber() || type == AtomType.ITEM; } /** * Checks the sequence extension. * @param it item * @param ii input info * @return same item * @throws QueryException query exception */ private Item check(final Item it, final InputInfo ii) throws QueryException { if(!checkExt(it)) XPCAST.thrw(ii, it.type.toString().replaceAll("\\(|\\)", ""), ext); return it; } /** * Checks the sequence extension. * @param it item * @return same item */ private boolean checkExt(final Item it) { return ext == null || ext.eq(((ANode) it).qname()); } /** * Checks the types for equality. * @param t type * @return result of check */ public boolean eq(final SeqType t) { return type == t.type && occ == t.occ; } /** * Checks if the specified SeqType is an instance of the current SeqType. * @param t SeqType to check * @return result of check */ public boolean instance(final SeqType t) { return type.instanceOf(t.type) && occ.instance(t.occ); } @Override public String toString() { final String str = type.toString(); return (str.contains(" ") && occ != Occ.O ? '(' + str + ')' : str) + occ; } }