package org.basex.data; import static org.basex.core.Text.*; import static org.basex.data.DataText.*; import static org.basex.util.Token.*; import java.io.IOException; import org.basex.build.BuildException; import org.basex.core.Context; import org.basex.core.MainProp; import org.basex.core.Prop; import org.basex.core.Users; import org.basex.core.cmd.DropDB; import org.basex.io.IO; import org.basex.io.IOFile; import org.basex.io.in.DataInput; import org.basex.io.out.DataOutput; import org.basex.util.*; import org.basex.util.ft.Language; /** * This class provides meta information on a database. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class MetaData { /** Database path. Set to {@code null} if database is in main memory. */ public final IOFile path; /** Properties. */ public final Prop prop; /** Database name. */ public String name; /** Database users. */ public Users users; /** Encoding of original document. */ public String encoding = UTF8; /** Path to original document. */ public String original = ""; /** Size of original document. */ public long filesize; /** Number of stored documents. */ public int ndocs; /** Timestamp of original document. */ public long time; /** Flag for whitespace chopping. */ public boolean chop; /** Flag for activated automatic index update. */ public boolean updindex; /** Indicates if a text index exists. */ public boolean textindex; /** Indicates if a attribute index exists. */ public boolean attrindex; /** Indicates if a full-text index exists. */ public boolean ftxtindex; /** Indicates if a path index exists. */ public boolean pathindex; /** Indicates if text index is to be recreated. */ public boolean createtext; /** Indicates if attribute index is to be recreated. */ public boolean createattr; /** Indicates if full-text index is to be recreated. */ public boolean createftxt; /** Indicates if path index is to be recreated. */ public boolean createpath; /** Flag for wildcard indexing. */ public boolean wildcards; /** Flag for full-text stemming. */ public boolean stemming; /** Flag for full-text case sensitivity. */ public boolean casesens; /** Flag for full-text diacritics removal. */ public boolean diacritics; /** Maximal indexed full-text score. */ public int maxscore; /** Minimal indexed full-text score. */ public int minscore; /** Scoring mode: see {@link Prop#SCORING}. */ public int scoring; /** Maximum number of categories. */ public int maxcats; /** Maximum token length. */ public int maxlen; /** Language of full-text search index. */ public Language language; /** Flag for out-of-date index structures. * Will be removed as soon as all indexes support updates. */ public boolean uptodate = true; /** Flag for out-of-date indexes. */ public boolean oldindex; /** Flag to indicate possible corruption. */ public boolean corrupt; /** Dirty flag. */ public boolean dirty; /** Table size. */ public int size; /** Last (highest) id assigned to a node. */ public int lastid = -1; /** * Constructor, specifying the database properties. * @param pr database properties */ public MetaData(final Prop pr) { this("", pr, null); } /** * Constructor, specifying the database name and context. * @param db database name * @param ctx database context */ public MetaData(final String db, final Context ctx) { this(db, ctx.prop, ctx.mprop); } /** * Constructor, specifying the database name. * @param db database name * @param pr database properties * @param mprop main properties */ public MetaData(final String db, final Prop pr, final MainProp mprop) { path = mprop != null ? mprop.dbpath(db) : null; prop = pr; name = db; chop = prop.is(Prop.CHOP); createtext = prop.is(Prop.TEXTINDEX); createattr = prop.is(Prop.ATTRINDEX); createftxt = prop.is(Prop.FTINDEX); createpath = prop.is(Prop.PATHINDEX); diacritics = prop.is(Prop.DIACRITICS); wildcards = prop.is(Prop.WILDCARDS); stemming = prop.is(Prop.STEMMING); casesens = prop.is(Prop.CASESENS); updindex = prop.is(Prop.UPDINDEX); scoring = prop.num(Prop.SCORING); maxlen = prop.num(Prop.MAXLEN); maxcats = prop.num(Prop.MAXCATS); language = Language.get(prop); users = new Users(false); } // STATIC METHODS ========================================================== /** * Checks if the specified file path refers to the specified database. * @param path file path * @param db database name * @param mprop main properties * @return result of check */ public static boolean found(final String path, final String db, final MainProp mprop) { // return true if the database exists and if the // specified path and database name equal each other final IOFile file = mprop.dbpath(db); final boolean exists = file.exists(); if(!exists || path.equals(db)) return exists; final IO io = IO.get(path); DataInput in = null; try { // return true if the storage version is up-to-date and // if the original and the specified file path and date are equal in = new DataInput(file(file, DATAINF)); boolean ok = true; int i = 3; String k; while(i != 0 && !(k = string(in.readToken())).isEmpty()) { final String v = string(in.readToken()); if(k.equals(DBSTR)) { ok &= STORAGE.equals(v) || new Version(STORAGE).compareTo( new Version(v)) > 0; i--; } else if(k.equals(DBFNAME)) { ok &= io.eq(IO.get(v)); i--; } else if(k.equals(DBTIME)) { ok &= io.timeStamp() == toLong(v); i--; } } return i == 0 && ok; } catch(final IOException ex) { Util.debug(ex); return false; } finally { if(in != null) try { in.close(); } catch(final IOException ex) { } } } /** * Normalizes a database path. Converts backslashes and * removes duplicate and leading slashes. * Returns {@code null} if the path contains invalid characters. * @param path input path * @return normalized path, or {@code null} */ public static String normPath(final String path) { final StringBuilder sb = new StringBuilder(); boolean slash = false; for(int p = 0; p < path.length(); p++) { final char c = path.charAt(p); if(c == '\\' || c == '/') { if(!slash && p != 0) sb.append('/'); slash = true; } else { if(Prop.WIN && ":*?\"<>\\|".indexOf(c) != -1) return null; if(slash) slash = false; sb.append(c); } } return sb.toString(); } /** * Checks if the specified database name is valid, matching the pattern * {@code [-\w]+}, or {@code [-\w*?,]+} if the glob flag is activated. * @param name name to be checked * @param glob allow glob syntax * @return result of check */ public static boolean validName(final String name, final boolean glob) { if(name == null) return false; // faster than a regular expression.. final int nl = name.length(); for(int n = 0; n < nl; n++) { final char ch = name.charAt(n); if((!glob || ch != '?' && ch != '*' && ch != ',') && !letterOrDigit(ch) && ch != '-') return false; } return nl != 0; } // PUBLIC METHODS =========================================================== /** * Returns the disk size of the database. * @return database size */ public long dbsize() { return path != null ? dbsize(path) : 0; } /** * Returns the disk timestamp of the database. * @return database size */ public long dbtime() { return path != null ? path.timeStamp() : 0; } /** * Calculates the database size. * @param io current file * @return file length */ private static long dbsize(final IOFile io) { long s = 0; if(io.isDir()) { for(final IOFile f : io.children()) s += dbsize(f); } else { s += io.length(); } return s; } /** * Returns a file instance for the specified database file. * Should only be called if database is disk-based. * @param fn filename * @return database filename */ public IOFile dbfile(final String fn) { return file(path, fn); } /** * Returns the binary directory. * @return binary directory */ public IOFile binaries() { return new IOFile(path, M_RAW); } /** * Returns the specified binary file, or {@code null} if the resource * path cannot be resolved (e.g. if it points to a parent directory). * @param pth internal file path * @return binary directory */ public IOFile binary(final String pth) { final IOFile dir = binaries(); final IOFile file = new IOFile(dir, pth); return file.path().startsWith(dir.path()) ? file : null; } /** * Drops the specified database files. * Should only be called if database is disk-based. * @param pat file pattern, or {@code null} if all files are to be deleted * @return result of check */ public synchronized boolean drop(final String pat) { return path != null && DropDB.drop(path, pat + IO.BASEXSUFFIX); } /** * Reads in meta data from the specified stream. * @param in input stream * @throws IOException I/O exception */ public void read(final DataInput in) throws IOException { String storage = "", istorage = ""; while(true) { final String k = string(in.readToken()); if(k.isEmpty()) break; if(k.equals(DBPERM)) { users.read(in); } else { final String v = string(in.readToken()); if(k.equals(DBSTR)) storage = v; else if(k.equals(IDBSTR)) istorage = v; else if(k.equals(DBFNAME)) original = v; else if(k.equals(DBENC)) encoding = v; else if(k.equals(DBSIZE)) size = toInt(v); else if(k.equals(DBNDOCS)) ndocs = toInt(v); else if(k.equals(DBSCMAX)) maxscore = toInt(v); else if(k.equals(DBSCMIN)) minscore = toInt(v); else if(k.equals(DBSCTYPE)) scoring = toInt(v); else if(k.equals(DBMAXLEN)) maxlen = toInt(v); else if(k.equals(DBMAXCATS)) maxcats = toInt(v); else if(k.equals(DBLASTID)) lastid = toInt(v); else if(k.equals(DBTIME)) time = toLong(v); else if(k.equals(DBFSIZE)) filesize = toLong(v); else if(k.equals(DBFTDC)) diacritics = toBool(v); else if(k.equals(DBCHOP)) chop = toBool(v); else if(k.equals(DBUPDIDX)) updindex = toBool(v); else if(k.equals(DBPTHIDX)) pathindex = toBool(v); else if(k.equals(DBTXTIDX)) textindex = toBool(v); else if(k.equals(DBATVIDX)) attrindex = toBool(v); else if(k.equals(DBFTXIDX)) ftxtindex = toBool(v); else if(k.equals(DBCRTPTH)) createpath = toBool(v); else if(k.equals(DBCRTTXT)) createtext = toBool(v); else if(k.equals(DBCRTATV)) createattr = toBool(v); else if(k.equals(DBCRTFTX)) createftxt = toBool(v); else if(k.equals(DBWCIDX)) wildcards = toBool(v); else if(k.equals(DBFTST)) stemming = toBool(v); else if(k.equals(DBFTCS)) casesens = toBool(v); else if(k.equals(DBFTDC)) diacritics = toBool(v); else if(k.equals(DBUPTODATE)) uptodate = toBool(v); else if(k.equals(DBFTLN)) language = Language.get(v); } } // check version of database storage if(!storage.equals(STORAGE) && new Version(storage).compareTo(new Version( STORAGE)) > 0) throw new BuildException(H_DB_FORMAT, storage); // check version of database indexes oldindex = !istorage.equals(ISTORAGE) && new Version(istorage).compareTo(new Version(ISTORAGE)) > 0; corrupt = dbfile(DATAUPD).exists(); } /** * Writes the meta data to the specified output stream. * @param out output stream * @throws IOException I/O Exception */ void write(final DataOutput out) throws IOException { writeInfo(out, DBSTR, STORAGE); writeInfo(out, DBFNAME, original); writeInfo(out, DBTIME, time); writeInfo(out, IDBSTR, ISTORAGE); writeInfo(out, DBFSIZE, filesize); writeInfo(out, DBNDOCS, ndocs); writeInfo(out, DBENC, encoding); writeInfo(out, DBSIZE, size); writeInfo(out, DBCHOP, chop); writeInfo(out, DBUPDIDX, updindex); writeInfo(out, DBPTHIDX, pathindex); writeInfo(out, DBTXTIDX, textindex); writeInfo(out, DBATVIDX, attrindex); writeInfo(out, DBFTXIDX, ftxtindex); writeInfo(out, DBCRTPTH, createpath); writeInfo(out, DBCRTTXT, createtext); writeInfo(out, DBCRTATV, createattr); writeInfo(out, DBCRTFTX, createftxt); writeInfo(out, DBWCIDX, wildcards); writeInfo(out, DBFTST, stemming); writeInfo(out, DBFTCS, casesens); writeInfo(out, DBFTDC, diacritics); writeInfo(out, DBSCMAX, maxscore); writeInfo(out, DBSCMIN, minscore); writeInfo(out, DBSCTYPE, scoring); writeInfo(out, DBMAXLEN, maxlen); writeInfo(out, DBMAXCATS, maxcats); writeInfo(out, DBUPTODATE, uptodate); writeInfo(out, DBLASTID, lastid); if(language != null) writeInfo(out, DBFTLN, language.toString()); out.writeToken(token(DBPERM)); users.write(out); out.write(0); } /** * Notifies the meta structures of an update and invalidates the indexes. */ void update() { // update database timestamp time = System.currentTimeMillis(); uptodate = false; dirty = true; if(!updindex) { textindex = false; attrindex = false; } ftxtindex = false; } // PRIVATE METHODS ========================================================== /** * Converts the specified string to a boolean value. * @param v value * @return result */ private static boolean toBool(final String v) { return v.equals("1"); } /** * Writes a boolean property to the specified output. * @param out output stream * @param k key * @param pr property to write * @throws IOException I/O exception */ private static void writeInfo(final DataOutput out, final String k, final boolean pr) throws IOException { writeInfo(out, k, pr ? "1" : "0"); } /** * Writes a numeric property to the specified output. * @param out output stream * @param k key * @param v value * @throws IOException I/O exception */ private static void writeInfo(final DataOutput out, final String k, final long v) throws IOException { writeInfo(out, k, Long.toString(v)); } /** * Writes a string property to the specified output. * @param out output stream * @param k key * @param v value * @throws IOException I/O exception */ private static void writeInfo(final DataOutput out, final String k, final String v) throws IOException { out.writeToken(token(k)); out.writeToken(token(v)); } /** * Creates a database file instance. * @param path database path * @param fn filename * @return database filename */ private static IOFile file(final IOFile path, final String fn) { return new IOFile(path, fn + IO.BASEXSUFFIX); } }