예제 #1
0
  /**
   * Chooses files that match the specified pattern.
   *
   * @param file file filter
   * @param content content filter
   * @param root root directory
   * @return sorted file paths
   * @throws InterruptedException interruption
   */
  String[] filter(final String file, final String content, final IOFile root)
      throws InterruptedException {

    final long id = ++filterId;
    final TreeSet<String> results = new TreeSet<>();
    final int[] search = new TokenParser(Token.lc(Token.token(content))).toArray();

    // glob pattern
    final ProjectCache pc = cache(root);
    if (file.contains("*") || file.contains("?")) {
      final Pattern pt = Pattern.compile(IOFile.regex(file));
      for (final String path : pc) {
        final int offset = offset(path, true);
        if (pt.matcher(path.substring(offset)).matches() && filterContent(path, search)) {
          results.add(path);
          if (results.size() >= MAXHITS) break;
        }
        if (id != filterId) throw new InterruptedException();
      }
    } else {
      // starts-with, contains, camel case
      final String pttrn = file.toLowerCase(Locale.ENGLISH).replace('\\', '/');
      final HashSet<String> exclude = new HashSet<>();
      final boolean pathSearch = pttrn.indexOf('/') != -1;
      for (int i = 0; i < (pathSearch ? 2 : 3); i++) {
        filter(pttrn, search, i, results, exclude, pathSearch, pc, id);
      }
    }
    return results.toArray(new String[results.size()]);
  }
예제 #2
0
  /**
   * Handles info messages resulting from a query execution.
   *
   * @param msg info message
   * @return true if error was found
   */
  private boolean error(final String msg) {
    final String line = msg.replaceAll("[\\r\\n].*", "");
    Matcher m = XQERROR.matcher(line);
    int el, ec = 2;
    if (!m.matches()) {
      m = XMLERROR.matcher(line);
      if (!m.matches()) return true;
      el = Integer.parseInt(m.group(1));
      errFile = getEditor().file.path();
    } else {
      el = Integer.parseInt(m.group(1));
      ec = Integer.parseInt(m.group(2));
      errFile = m.group(3);
    }

    final EditorArea edit = find(IO.get(errFile), false);
    if (edit == null) return true;

    // find approximate error position
    final int ll = edit.last.length;
    int ep = ll;
    for (int e = 1, l = 1, c = 1; e < ll; ++c, e += cl(edit.last, e)) {
      if (l > el || l == el && c == ec) {
        ep = e;
        break;
      }
      if (edit.last[e] == '\n') {
        ++l;
        c = 0;
      }
    }
    if (ep < ll && Character.isLetterOrDigit(cp(edit.last, ep))) {
      while (ep > 0 && Character.isLetterOrDigit(cp(edit.last, ep - 1))) ep--;
    }
    edit.error(ep);
    errPos = ep;
    return true;
  }
예제 #3
0
/**
 * This view allows the input and evaluation of queries and documents.
 *
 * @author BaseX Team 2005-12, BSD License
 * @author Christian Gruen
 */
public final class EditorView extends View {
  /** Error string. */
  private static final String ERRSTRING =
      STOPPED_AT + ' ' + (LINE_X + ", " + COLUMN_X).replaceAll("%", "([0-9]+)");
  /** XQuery error pattern. */
  private static final Pattern XQERROR =
      Pattern.compile(ERRSTRING + ' ' + IN_FILE_X.replaceAll("%", "(.*?)") + COL);
  /** XML error pattern. */
  private static final Pattern XMLERROR =
      Pattern.compile(LINE_X.replaceAll("%", "(.*?)") + COL + ".*");

  /** History Button. */
  final BaseXButton hist;
  /** Execute Button. */
  final BaseXButton stop;
  /** Info label. */
  final BaseXLabel info;
  /** Position label. */
  final BaseXLabel pos;
  /** Query area. */
  final BaseXTabs tabs;
  /** Execute button. */
  final BaseXButton go;
  /** Thread counter. */
  int threadID;

  /** File in which the most recent error occurred. */
  String errFile;
  /** Most recent error position; used for clicking on error message. */
  int errPos;

  /** Header string. */
  private final BaseXLabel header;
  /** Filter button. */
  private final BaseXButton filter;

  /** Search panel. */
  public final SearchPanel search;

  /**
   * Default constructor.
   *
   * @param man view manager
   */
  public EditorView(final ViewNotifier man) {
    super(EDITORVIEW, man);
    if (Prop.langright) applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);

    border(6, 6, 6, 6).layout(new BorderLayout(0, 2)).setFocusable(false);

    header = new BaseXLabel(EDITOR, true, false);

    final BaseXButton srch = new BaseXButton(gui, "search", H_REPLACE);
    final BaseXButton openB = BaseXButton.command(GUICommands.C_EDITOPEN, gui);
    final BaseXButton saveB = new BaseXButton(gui, "save", H_SAVE);
    hist = new BaseXButton(gui, "hist", H_RECENTLY_OPEN);

    final BaseXBack buttons = new BaseXBack(Fill.NONE);
    buttons.layout(new TableLayout(1, 4, 1, 0));
    buttons.add(srch);
    buttons.add(openB);
    buttons.add(saveB);
    buttons.add(hist);

    final BaseXBack b = new BaseXBack(Fill.NONE).layout(new BorderLayout(8, 0));
    if (Prop.langright) {
      b.add(header, BorderLayout.EAST);
      b.add(buttons, BorderLayout.WEST);
    } else {
      b.add(header, BorderLayout.CENTER);
      b.add(buttons, BorderLayout.EAST);
    }
    add(b, BorderLayout.NORTH);

    tabs = new BaseXTabs(gui);
    tabs.setFocusable(false);
    final SearchEditor se = new SearchEditor(gui, tabs, null).button(srch);
    search = se.panel();
    addCreateTab();
    add(se, BorderLayout.CENTER);

    // status and query pane
    search.editor(addTab(), false);

    info = new BaseXLabel().setText(OK, Msg.SUCCESS);
    pos = new BaseXLabel(" ");
    posCode.invokeLater();

    stop = new BaseXButton(gui, "stop", H_STOP_PROCESS);
    stop.addKeyListener(this);
    stop.setEnabled(false);

    go = new BaseXButton(gui, "go", H_EXECUTE_QUERY);
    go.addKeyListener(this);

    filter = BaseXButton.command(GUICommands.C_FILTER, gui);
    filter.addKeyListener(this);
    filter.setEnabled(false);

    final BaseXBack status = new BaseXBack(Fill.NONE).layout(new BorderLayout(4, 0));
    status.add(info, BorderLayout.CENTER);
    status.add(pos, BorderLayout.EAST);

    final BaseXBack query = new BaseXBack(Fill.NONE).layout(new TableLayout(1, 3, 1, 0));
    query.add(stop);
    query.add(go);
    query.add(filter);

    final BaseXBack south = new BaseXBack(Fill.NONE).border(4, 0, 0, 0);
    south.layout(new BorderLayout(8, 0));
    south.add(status, BorderLayout.CENTER);
    south.add(query, BorderLayout.EAST);
    add(south, BorderLayout.SOUTH);

    refreshLayout();

    // add listeners
    saveB.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            final JPopupMenu pop = new JPopupMenu();
            final StringBuilder mnem = new StringBuilder();
            final JMenuItem sa = GUIMenu.newItem(GUICommands.C_EDITSAVE, gui, mnem);
            final JMenuItem sas = GUIMenu.newItem(GUICommands.C_EDITSAVEAS, gui, mnem);
            GUICommands.C_EDITSAVE.refresh(gui, sa);
            GUICommands.C_EDITSAVEAS.refresh(gui, sas);
            pop.add(sa);
            pop.add(sas);
            pop.show(saveB, 0, saveB.getHeight());
          }
        });
    hist.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            final JPopupMenu pm = new JPopupMenu();
            final ActionListener al =
                new ActionListener() {
                  @Override
                  public void actionPerformed(final ActionEvent ac) {
                    open(new IOFile(ac.getActionCommand()));
                  }
                };
            final StringList sl = new StringList();
            for (final EditorArea ea : editors()) sl.add(ea.file.path());
            for (final String en :
                new StringList().add(gui.gprop.strings(GUIProp.EDITOR)).sort(!Prop.WIN, true)) {
              final JMenuItem it = new JMenuItem(en);
              it.setEnabled(!sl.contains(en));
              pm.add(it).addActionListener(al);
            }
            pm.show(hist, 0, hist.getHeight());
          }
        });
    refreshHistory(null);
    info.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mouseClicked(final MouseEvent e) {
            EditorArea ea = getEditor();
            if (errFile != null) {
              ea = find(IO.get(errFile), false);
              if (ea == null) ea = open(new IOFile(errFile));
              tabs.setSelectedComponent(ea);
            }
            if (errPos == -1) return;
            ea.jumpError(errPos);
            posCode.invokeLater();
          }
        });
    stop.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            stop.setEnabled(false);
            go.setEnabled(false);
            gui.stop();
          }
        });
    go.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            getEditor().release(Action.EXECUTE);
          }
        });
    tabs.addChangeListener(
        new ChangeListener() {
          @Override
          public void stateChanged(final ChangeEvent e) {
            final EditorArea ea = getEditor();
            if (ea == null) return;
            search.editor(ea, true);
            gui.refreshControls();
            posCode.invokeLater();
          }
        });

    BaseXLayout.addDrop(
        this,
        new DropHandler() {
          @Override
          public void drop(final Object file) {
            if (file instanceof File) open(new IOFile((File) file));
          }
        });
  }

  @Override
  public void refreshInit() {}

  @Override
  public void refreshFocus() {}

  @Override
  public void refreshMark() {
    final EditorArea edit = getEditor();
    go.setEnabled(edit.script || edit.xquery && !gui.gprop.is(GUIProp.EXECRT));
    final Nodes mrk = gui.context.marked;
    filter.setEnabled(!gui.gprop.is(GUIProp.FILTERRT) && mrk != null && mrk.size() != 0);
  }

  @Override
  public void refreshContext(final boolean more, final boolean quick) {}

  @Override
  public void refreshLayout() {
    header.setFont(GUIConstants.lfont);
    for (final EditorArea edit : editors()) edit.setFont(GUIConstants.mfont);
    search.refreshLayout();
  }

  @Override
  public void refreshUpdate() {}

  @Override
  public boolean visible() {
    return gui.gprop.is(GUIProp.SHOWEDITOR);
  }

  @Override
  public void visible(final boolean v) {
    gui.gprop.set(GUIProp.SHOWEDITOR, v);
  }

  @Override
  protected boolean db() {
    return false;
  }

  /** Opens a new file. */
  public void open() {
    // open file chooser for XML creation
    final BaseXFileChooser fc = new BaseXFileChooser(OPEN, gui.gprop.get(GUIProp.WORKPATH), gui);
    fc.filter(BXS_FILES, IO.BXSSUFFIX);
    fc.filter(XQUERY_FILES, IO.XQSUFFIXES);
    fc.filter(XML_DOCUMENTS, IO.XMLSUFFIXES);

    final IOFile[] files = fc.multi().selectAll(Mode.FOPEN);
    for (final IOFile f : files) open(f);
  }

  /** Reverts the contents of the currently opened editor. */
  public void reopen() {
    getEditor().reopen(true);
  }

  /**
   * Saves the contents of the currently opened editor.
   *
   * @return {@code false} if operation was canceled
   */
  public boolean save() {
    final EditorArea edit = getEditor();
    return edit.opened() ? save(edit.file) : saveAs();
  }

  /**
   * Saves the contents of the currently opened editor under a new name.
   *
   * @return {@code false} if operation was canceled
   */
  public boolean saveAs() {
    // open file chooser for XML creation
    final EditorArea edit = getEditor();
    final BaseXFileChooser fc =
        new BaseXFileChooser(SAVE_AS, edit.file.path(), gui).filter(XQUERY_FILES, IO.XQSUFFIXES);

    final IOFile file = fc.select(Mode.FSAVE);
    return file != null && save(file);
  }

  /** Creates a new file. */
  public void newFile() {
    addTab();
    refreshControls(true);
  }

  /**
   * Opens the specified query file.
   *
   * @param file query file
   * @return opened editor
   */
  public EditorArea open(final IOFile file) {
    if (!visible()) GUICommands.C_SHOWEDITOR.execute(gui);

    EditorArea edit = find(file, true);
    try {
      if (edit != null) {
        // display open file
        tabs.setSelectedComponent(edit);
        edit.reopen(true);
      } else {
        // get current editor
        edit = getEditor();
        // create new tab if current text is stored on disk or has been modified
        if (edit.opened() || edit.modified) edit = addTab();
        edit.initText(file.read());
        edit.file(file);
      }
    } catch (final IOException ex) {
      BaseXDialog.error(gui, FILE_NOT_OPENED);
    }
    return edit;
  }

  /**
   * Refreshes the list of recent query files and updates the query path.
   *
   * @param file new file
   */
  void refreshHistory(final IOFile file) {
    final StringList sl = new StringList();
    String path = null;
    if (file != null) {
      path = file.path();
      gui.gprop.set(GUIProp.WORKPATH, file.dirPath());
      sl.add(path);
      tabs.setToolTipTextAt(tabs.getSelectedIndex(), path);
    }
    final String[] qu = gui.gprop.strings(GUIProp.EDITOR);
    for (int q = 0; q < qu.length && q < 19; q++) {
      final String f = qu[q];
      if (!f.equalsIgnoreCase(path) && IO.get(f).exists()) sl.add(f);
    }
    // store sorted history
    gui.gprop.set(GUIProp.EDITOR, sl.toArray());
    hist.setEnabled(!sl.isEmpty());
  }

  /**
   * Closes an editor.
   *
   * @param edit editor to be closed. {@code null} closes the currently opened editor.
   * @return {@code true} if editor was closed
   */
  public boolean close(final EditorArea edit) {
    final EditorArea ea = edit != null ? edit : getEditor();
    if (!confirm(ea)) return false;

    tabs.remove(ea);
    final int t = tabs.getTabCount();
    final int i = tabs.getSelectedIndex();
    if (t == 1) {
      // reopen single tab
      addTab();
    } else if (i + 1 == t) {
      // if necessary, activate last editor tab
      tabs.setSelectedIndex(i - 1);
    }
    return true;
  }

  /** Jumps to a specific line. */
  public void gotoLine() {
    final EditorArea edit = getEditor();
    final int ll = edit.last.length;
    final int cr = edit.getCaret();
    int l = 1;
    for (int e = 0; e < ll && e < cr; e += cl(edit.last, e)) {
      if (edit.last[e] == '\n') ++l;
    }
    final DialogLine dl = new DialogLine(gui, l);
    if (!dl.ok()) return;
    final int el = dl.line();
    int p = 0;
    l = 1;
    for (int e = 0; e < ll && l < el; e += cl(edit.last, e)) {
      if (edit.last[e] != '\n') continue;
      p = e + 1;
      ++l;
    }
    edit.setCaret(p);
    posCode.invokeLater();
  }

  /** Starts a thread, which shows a waiting info after a short timeout. */
  public void start() {
    final int thread = threadID;
    new Thread() {
      @Override
      public void run() {
        Performance.sleep(200);
        if (thread == threadID) {
          info.setText(PLEASE_WAIT_D, Msg.SUCCESS).setToolTipText(null);
          stop.setEnabled(true);
        }
      }
    }.start();
  }

  /**
   * Evaluates the info message resulting from a parsed or executed query.
   *
   * @param msg info message
   * @param ok {@code true} if evaluation was successful
   * @param up update
   */
  public void info(final String msg, final boolean ok, final boolean up) {
    ++threadID;
    errPos = -1;
    errFile = null;
    getEditor().resetError();

    final String m =
        msg.replaceAll("^.*\r?\n\\[.*?\\]", "")
            .replaceAll(".*" + LINE_X.replaceAll("%", ".*?") + COL, "");
    if (ok) {
      info.setCursor(GUIConstants.CURSORARROW);
      info.setText(m, Msg.SUCCESS).setToolTipText(null);
    } else {
      info.setCursor(error(msg) ? GUIConstants.CURSORHAND : GUIConstants.CURSORARROW);
      info.setText(m, Msg.ERROR).setToolTipText(msg);
    }

    if (up) {
      stop.setEnabled(false);
      refreshMark();
    }
  }

  /**
   * Handles info messages resulting from a query execution.
   *
   * @param msg info message
   * @return true if error was found
   */
  private boolean error(final String msg) {
    final String line = msg.replaceAll("[\\r\\n].*", "");
    Matcher m = XQERROR.matcher(line);
    int el, ec = 2;
    if (!m.matches()) {
      m = XMLERROR.matcher(line);
      if (!m.matches()) return true;
      el = Integer.parseInt(m.group(1));
      errFile = getEditor().file.path();
    } else {
      el = Integer.parseInt(m.group(1));
      ec = Integer.parseInt(m.group(2));
      errFile = m.group(3);
    }

    final EditorArea edit = find(IO.get(errFile), false);
    if (edit == null) return true;

    // find approximate error position
    final int ll = edit.last.length;
    int ep = ll;
    for (int e = 1, l = 1, c = 1; e < ll; ++c, e += cl(edit.last, e)) {
      if (l > el || l == el && c == ec) {
        ep = e;
        break;
      }
      if (edit.last[e] == '\n') {
        ++l;
        c = 0;
      }
    }
    if (ep < ll && Character.isLetterOrDigit(cp(edit.last, ep))) {
      while (ep > 0 && Character.isLetterOrDigit(cp(edit.last, ep - 1))) ep--;
    }
    edit.error(ep);
    errPos = ep;
    return true;
  }

  /**
   * Shows a quit dialog for all modified query files.
   *
   * @return {@code false} if confirmation was canceled
   */
  public boolean confirm() {
    for (final EditorArea edit : editors()) {
      tabs.setSelectedComponent(edit);
      if (!close(edit)) return false;
    }
    return true;
  }

  /**
   * Checks if the current text can be saved or reverted.
   *
   * @param rev revert flag
   * @return result of check
   */
  public boolean modified(final boolean rev) {
    final EditorArea edit = getEditor();
    return edit.modified || !rev && !edit.opened();
  }

  /**
   * Returns the current editor.
   *
   * @return editor
   */
  public EditorArea getEditor() {
    final Component c = tabs.getSelectedComponent();
    return c instanceof EditorArea ? (EditorArea) c : null;
  }

  /**
   * Refreshes the query modification flag.
   *
   * @param force action
   */
  void refreshControls(final boolean force) {
    // update modification flag
    final EditorArea edit = getEditor();
    final boolean oe = edit.modified;
    edit.modified = edit.hist != null && edit.hist.modified();
    if (edit.modified == oe && !force) return;

    // update tab title
    String title = edit.file.name();
    if (edit.modified) title += '*';
    edit.label.setText(title);

    // update components
    gui.refreshControls();
    posCode.invokeLater();
  }

  /** Code for setting cursor position. */
  final GUICode posCode =
      new GUICode() {
        @Override
        public void eval(final Object arg) {
          final int[] lc = getEditor().pos();
          pos.setText(lc[0] + " : " + lc[1]);
        }
      };

  /**
   * Finds the editor that contains the specified file.
   *
   * @param file file to be found
   * @param opened considers only opened files
   * @return editor
   */
  EditorArea find(final IO file, final boolean opened) {
    for (final EditorArea edit : editors()) {
      if (edit.file.eq(file) && (!opened || edit.opened())) return edit;
    }
    return null;
  }

  /**
   * Saves the specified editor contents.
   *
   * @param file file to write
   * @return {@code false} if confirmation was canceled
   */
  private boolean save(final IOFile file) {
    try {
      final EditorArea edit = getEditor();
      file.write(edit.getText());
      edit.file(file);
      return true;
    } catch (final IOException ex) {
      BaseXDialog.error(gui, FILE_NOT_SAVED);
      return false;
    }
  }

  /**
   * Choose a unique tab file.
   *
   * @return io reference
   */
  private IOFile newTabFile() {
    // collect numbers of existing files
    final BoolList bl = new BoolList();
    for (final EditorArea edit : editors()) {
      if (edit.opened()) continue;
      final String n = edit.file.name().substring(FILE.length());
      bl.set(n.isEmpty() ? 1 : Integer.parseInt(n), true);
    }
    // find first free file number
    int c = 0;
    while (++c < bl.size() && bl.get(c)) ;
    // create io reference
    return new IOFile(gui.gprop.get(GUIProp.WORKPATH), FILE + (c == 1 ? "" : c));
  }

  /**
   * Adds a new editor tab.
   *
   * @return editor reference
   */
  EditorArea addTab() {
    final EditorArea edit = new EditorArea(this, newTabFile());
    edit.setFont(GUIConstants.mfont);

    final BaseXBack tab = new BaseXBack(new BorderLayout(10, 0)).mode(Fill.NONE);
    tab.add(edit.label, BorderLayout.CENTER);

    final BaseXButton close = tabButton("e_close");
    close.setRolloverIcon(BaseXLayout.icon("e_close2"));
    close.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            close(edit);
          }
        });
    tab.add(close, BorderLayout.EAST);

    tabs.add(edit, tab, tabs.getComponentCount() - 2);
    return edit;
  }

  /** Adds a tab for creating new tabs. */
  private void addCreateTab() {
    final BaseXButton add = tabButton("e_new");
    add.setRolloverIcon(BaseXLayout.icon("e_new2"));
    add.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(final ActionEvent e) {
            addTab();
            refreshControls(true);
          }
        });
    tabs.add(new BaseXBack(), add, 0);
    tabs.setEnabledAt(0, false);
  }

  /**
   * Adds a new tab button.
   *
   * @param icon button icon
   * @return button
   */
  private BaseXButton tabButton(final String icon) {
    final BaseXButton b = new BaseXButton(gui, icon, null);
    b.border(2, 2, 2, 2).setContentAreaFilled(false);
    b.setFocusable(false);
    return b;
  }

  /**
   * Shows a quit dialog for the specified editor.
   *
   * @param edit editor to be saved
   * @return {@code false} if confirmation was canceled
   */
  private boolean confirm(final EditorArea edit) {
    if (edit.modified && (edit.opened() || edit.getText().length != 0)) {
      final Boolean ok = BaseXDialog.yesNoCancel(gui, Util.info(CLOSE_FILE_X, edit.file.name()));
      if (ok == null || ok && !save()) return false;
    }
    return true;
  }

  /**
   * Returns all editors.
   *
   * @return editors
   */
  EditorArea[] editors() {
    final ArrayList<EditorArea> edits = new ArrayList<EditorArea>();
    for (final Component c : tabs.getComponents()) {
      if (c instanceof EditorArea) edits.add((EditorArea) c);
    }
    return edits.toArray(new EditorArea[edits.size()]);
  }
}
예제 #4
0
 /**
  * Checks if the specified XQuery string is a library module.
  *
  * @param qu query string
  * @return result of check
  */
 public static boolean isLibrary(final String qu) {
   return LIBMOD_PATTERN.matcher(removeComments(qu, 80)).matches();
 }
예제 #5
0
/**
 * This class is an entry point for evaluating XQuery strings.
 *
 * @author BaseX Team 2005-15, BSD License
 * @author Christian Gruen
 */
public final class QueryProcessor extends Proc implements AutoCloseable {
  /** Pattern for detecting library modules. */
  private static final Pattern LIBMOD_PATTERN =
      Pattern.compile(
          "^(xquery( version ['\"].*?['\"])?( encoding ['\"].*?['\"])? ?; ?)?module namespace.*");

  /** Static context. */
  public final StaticContext sc;
  /** Expression context. */
  public final QueryContext qc;
  /** Query. */
  private final String query;
  /** Parsed flag. */
  private boolean parsed;

  /**
   * Default constructor.
   *
   * @param query query string
   * @param ctx database context
   */
  public QueryProcessor(final String query, final Context ctx) {
    this.query = query;
    qc = proc(new QueryContext(ctx));
    sc = new StaticContext(ctx);
  }

  /**
   * Parses the query.
   *
   * @throws QueryException query exception
   */
  public void parse() throws QueryException {
    if (parsed) return;
    parsed = true;
    qc.parseMain(query, null, sc);
    updating = qc.updating;
  }

  /**
   * Compiles the query.
   *
   * @throws QueryException query exception
   */
  public void compile() throws QueryException {
    parse();
    qc.compile();
  }

  /**
   * Returns a result iterator.
   *
   * @return result iterator
   * @throws QueryException query exception
   */
  public Iter iter() throws QueryException {
    parse();
    return qc.iter();
  }

  /**
   * Returns a result value.
   *
   * @return result value
   * @throws QueryException query exception
   */
  public Value value() throws QueryException {
    parse();
    return qc.iter().value();
  }

  /**
   * Evaluates the specified query and returns the result.
   *
   * @return result of query
   * @throws QueryException query exception
   */
  public Result execute() throws QueryException {
    parse();
    return qc.execute();
  }

  /**
   * Binds a value with the specified type to a global variable. If the value is an {@link Expr}
   * instance, it is directly assigned. Otherwise, it is first cast to the appropriate XQuery type.
   * If {@code "json"} is specified as type, the value is interpreted according to the rules
   * specified in {@link JsonMapConverter}.
   *
   * @param name name of variable
   * @param value value to be bound
   * @param type type (may be {@code null})
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor bind(final String name, final Object value, final String type)
      throws QueryException {
    qc.bind(name, value, type, sc);
    return this;
  }

  /**
   * Binds a value to a global variable.
   *
   * @param name name of variable
   * @param value value to be bound
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor bind(final String name, final Object value) throws QueryException {
    return bind(name, value, null);
  }

  /**
   * Binds an XQuery value to a global variable.
   *
   * @param name name of variable
   * @param value value to be bound
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor bind(final String name, final Value value) throws QueryException {
    qc.bind(name, value, sc);
    return this;
  }

  /**
   * Binds the context value.
   *
   * @param value value to be bound
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor context(final Object value) throws QueryException {
    return context(value, null);
  }

  /**
   * Binds the context value.
   *
   * @param value XQuery value to be bound
   * @return self reference
   */
  public QueryProcessor context(final Value value) {
    qc.context(value, sc);
    return this;
  }

  /**
   * Binds the HTTP context to the query processor.
   *
   * @param value HTTP context
   * @return self reference
   */
  public QueryProcessor http(final Object value) {
    qc.http(value);
    return this;
  }

  /**
   * Binds the context value with a specified type, using the same rules as for {@link #bind binding
   * variables}.
   *
   * @param value value to be bound
   * @param type type (may be {@code null})
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor context(final Object value, final String type) throws QueryException {
    qc.context(value, type, sc);
    return this;
  }

  /**
   * Declares a namespace. A namespace is undeclared if the {@code uri} is an empty string. The
   * default element namespaces is set if the {@code prefix} is empty.
   *
   * @param prefix namespace prefix
   * @param uri namespace uri
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor namespace(final String prefix, final String uri) throws QueryException {
    sc.namespace(prefix, uri);
    return this;
  }

  /**
   * Returns a serializer for the given output stream. Optional output declarations within the query
   * will be included in the serializer instance.
   *
   * @param os output stream
   * @return serializer instance
   * @throws IOException query exception
   * @throws QueryException query exception
   */
  public Serializer getSerializer(final OutputStream os) throws IOException, QueryException {
    compile();
    try {
      return Serializer.get(os, qc.serParams());
    } catch (final QueryIOException ex) {
      throw ex.getCause();
    }
  }

  /**
   * Adds a module reference. Only called from the test APIs.
   *
   * @param uri module uri
   * @param file file name
   */
  public void module(final String uri, final String file) {
    qc.modDeclared.put(uri, file);
  }

  /**
   * Returns the query string.
   *
   * @return query
   */
  public String query() {
    return query;
  }

  @Override
  public void close() {
    qc.close();
  }

  @Override
  public void databases(final LockResult lr) {
    qc.databases(lr);
  }

  /**
   * Returns the number of performed updates after query execution, or {@code 0}.
   *
   * @return number of updates
   */
  public int updates() {
    return updating ? qc.resources.updates().size() : 0;
  }

  /**
   * Returns query information.
   *
   * @return query information
   */
  public String info() {
    return qc.info();
  }

  /**
   * Checks if the specified XQuery string is a library module.
   *
   * @param qu query string
   * @return result of check
   */
  public static boolean isLibrary(final String qu) {
    return LIBMOD_PATTERN.matcher(removeComments(qu, 80)).matches();
  }

  /**
   * Removes comments from the specified string and returns the first characters of a query.
   *
   * @param qu query string
   * @param max maximum length of string to return
   * @return result
   */
  public static String removeComments(final String qu, final int max) {
    final StringBuilder sb = new StringBuilder();
    int m = 0;
    boolean s = false;
    final int cl = qu.length();
    for (int c = 0; c < cl && sb.length() < max; ++c) {
      final char ch = qu.charAt(c);
      if (ch == 0x0d) continue;
      if (ch == '(' && c + 1 < cl && qu.charAt(c + 1) == ':') {
        if (m == 0 && !s) {
          sb.append(' ');
          s = true;
        }
        ++m;
        ++c;
      } else if (m != 0 && ch == ':' && c + 1 < cl && qu.charAt(c + 1) == ')') {
        --m;
        ++c;
      } else if (m == 0) {
        if (ch > ' ') sb.append(ch);
        else if (!s) sb.append(' ');
        s = ch <= ' ';
      }
    }
    if (sb.length() >= max) sb.append("...");
    return sb.toString().trim();
  }

  /**
   * Returns a map with variable bindings.
   *
   * @param opts main options
   * @return bindings
   */
  public static HashMap<String, String> bindings(final MainOptions opts) {
    final HashMap<String, String> bindings = new HashMap<>();
    final String bind = opts.get(MainOptions.BINDINGS).trim();
    final StringBuilder key = new StringBuilder();
    final StringBuilder val = new StringBuilder();
    boolean first = true;
    final int sl = bind.length();
    for (int s = 0; s < sl; s++) {
      final char ch = bind.charAt(s);
      if (first) {
        if (ch == '=') {
          first = false;
        } else {
          key.append(ch);
        }
      } else {
        if (ch == ',') {
          if (s + 1 == sl || bind.charAt(s + 1) != ',') {
            bindings.put(key.toString().trim(), val.toString());
            key.setLength(0);
            val.setLength(0);
            first = true;
            continue;
          }
          // literal commas are escaped by a second comma
          s++;
        }
        val.append(ch);
      }
    }
    if (key.length() != 0) bindings.put(key.toString().trim(), val.toString());
    return bindings;
  }

  /**
   * Returns a tree representation of the query plan.
   *
   * @return root node
   */
  public FDoc plan() {
    return new FDoc().add(qc.plan());
  }

  @Override
  public String tit() {
    return PLEASE_WAIT_D;
  }

  @Override
  public String det() {
    return PLEASE_WAIT_D;
  }

  @Override
  public String toString() {
    return query;
  }
}
예제 #6
0
/**
 * This class is an entry point for evaluating XQuery strings.
 *
 * @author BaseX Team 2005-16, BSD License
 * @author Christian Gruen
 */
public final class QueryProcessor extends Job implements Closeable {
  /** Pattern for detecting library modules. */
  private static final Pattern LIBMOD_PATTERN =
      Pattern.compile(
          "^(xquery( version ['\"].*?['\"])?( encoding ['\"].*?['\"])? ?; ?)?module namespace.*");

  /** Static context. */
  public final StaticContext sc;
  /** Expression context. */
  public final QueryContext qc;
  /** Query. */
  private final String query;
  /** Parsed flag. */
  private boolean parsed;

  /**
   * Default constructor.
   *
   * @param query query string
   * @param ctx database context
   */
  public QueryProcessor(final String query, final Context ctx) {
    this(query, null, ctx);
  }

  /**
   * Default constructor.
   *
   * @param query query string
   * @param uri base uri (can be {@code null})
   * @param ctx database context
   */
  public QueryProcessor(final String query, final String uri, final Context ctx) {
    this.query = query;
    qc = pushJob(new QueryContext(ctx));
    sc = new StaticContext(qc);
    sc.baseURI(uri);
  }

  /**
   * Parses the query.
   *
   * @throws QueryException query exception
   */
  public void parse() throws QueryException {
    if (parsed) return;
    try {
      qc.parseMain(query, null, sc);
    } finally {
      parsed = true;
      updating = qc.updating;
    }
  }

  /**
   * Compiles the query.
   *
   * @throws QueryException query exception
   */
  public void compile() throws QueryException {
    parse();
    qc.compile();
  }

  /**
   * Returns a memory-efficient result iterator. In most cases, the query will only be fully
   * evaluated if all items of this iterator are requested.
   *
   * @return result iterator
   * @throws QueryException query exception
   */
  public Iter iter() throws QueryException {
    parse();
    return qc.iter();
  }

  /**
   * Evaluates the query and returns the resulting value.
   *
   * @return result value
   * @throws QueryException query exception
   */
  public Value value() throws QueryException {
    parse();
    return qc.iter().value();
  }

  /**
   * This function is called by the GUI; use {@link #iter()} or {@link #value()} instead. Caches and
   * returns the result of the specified query. If all nodes are of the same database instance, the
   * returned value will be of type {@link DBNodes}.
   *
   * @param max maximum number of results to cache (negative: return all values)
   * @return result of query
   * @throws QueryException query exception
   */
  public Value cache(final int max) throws QueryException {
    parse();
    return qc.cache(max);
  }

  /**
   * Binds a value with the specified type to a global variable. If the value is an {@link Expr}
   * instance, it is directly assigned. Otherwise, it is first cast to the appropriate XQuery type.
   * If {@code "json"} is specified as type, the value is interpreted according to the rules
   * specified in {@link JsonMapConverter}.
   *
   * @param name name of variable
   * @param value value to be bound
   * @param type type (may be {@code null})
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor bind(final String name, final Object value, final String type)
      throws QueryException {
    qc.bind(name, value, type, sc);
    return this;
  }

  /**
   * Binds a value to a global variable.
   *
   * @param name name of variable
   * @param value value to be bound
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor bind(final String name, final Object value) throws QueryException {
    return bind(name, value, null);
  }

  /**
   * Binds an XQuery value to a global variable.
   *
   * @param name name of variable
   * @param value value to be bound
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor bind(final String name, final Value value) throws QueryException {
    qc.bind(name, value, sc);
    return this;
  }

  /**
   * Binds the context value.
   *
   * @param value value to be bound
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor context(final Object value) throws QueryException {
    return context(value, null);
  }

  /**
   * Binds the context value.
   *
   * @param value XQuery value to be bound
   * @return self reference
   */
  public QueryProcessor context(final Value value) {
    qc.context(value, sc);
    return this;
  }

  /**
   * Binds the HTTP context to the query processor.
   *
   * @param value HTTP context
   * @return self reference
   */
  public QueryProcessor http(final Object value) {
    qc.http(value);
    return this;
  }

  /**
   * Binds the context value with a specified type, using the same rules as for {@link #bind binding
   * variables}.
   *
   * @param value value to be bound
   * @param type type (may be {@code null})
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor context(final Object value, final String type) throws QueryException {
    qc.context(value, type, sc);
    return this;
  }

  /**
   * Declares a namespace. A namespace is undeclared if the {@code uri} is an empty string. The
   * default element namespaces is set if the {@code prefix} is empty.
   *
   * @param prefix namespace prefix
   * @param uri namespace uri
   * @return self reference
   * @throws QueryException query exception
   */
  public QueryProcessor namespace(final String prefix, final String uri) throws QueryException {
    sc.namespace(prefix, uri);
    return this;
  }

  /**
   * Assigns a URI resolver.
   *
   * @param resolver resolver
   * @return self reference
   */
  public QueryProcessor uriResolver(final UriResolver resolver) {
    sc.resolver = resolver;
    return this;
  }

  /**
   * Returns a serializer for the given output stream. Optional output declarations within the query
   * will be included in the serializer instance.
   *
   * @param os output stream
   * @return serializer instance
   * @throws IOException query exception
   * @throws QueryException query exception
   */
  public Serializer getSerializer(final OutputStream os) throws IOException, QueryException {
    compile();
    try {
      return Serializer.get(os, qc.serParams()).sc(sc);
    } catch (final QueryIOException ex) {
      throw ex.getCause();
    }
  }

  /**
   * Adds a module reference. Only called from the test APIs.
   *
   * @param uri module uri
   * @param file file name
   */
  public void module(final String uri, final String file) {
    qc.modDeclared.put(uri, file);
  }

  /**
   * Returns the query string.
   *
   * @return query
   */
  public String query() {
    return query;
  }

  @Override
  public void close() {
    qc.close();
  }

  @Override
  public void databases(final LockResult lr) {
    qc.databases(lr);
  }

  /**
   * Returns the number of performed updates after query execution, or {@code 0}.
   *
   * @return number of updates
   */
  public int updates() {
    return updating ? qc.updates().size() : 0;
  }

  /**
   * Returns query information.
   *
   * @return query information
   */
  public String info() {
    return qc.info();
  }

  /**
   * Checks if the specified XQuery string is a library module.
   *
   * @param qu query string
   * @return result of check
   */
  public static boolean isLibrary(final String qu) {
    return LIBMOD_PATTERN.matcher(removeComments(qu, 80)).matches();
  }

  /**
   * Removes comments from the specified string and returns the first characters of a query.
   *
   * @param qu query string
   * @param max maximum length of string to return
   * @return result
   */
  public static String removeComments(final String qu, final int max) {
    final StringBuilder sb = new StringBuilder();
    int m = 0;
    boolean s = false;
    final int cl = qu.length();
    for (int c = 0; c < cl && sb.length() < max; ++c) {
      final char ch = qu.charAt(c);
      if (ch == 0x0d) continue;
      if (ch == '(' && c + 1 < cl && qu.charAt(c + 1) == ':') {
        if (m == 0 && !s) {
          sb.append(' ');
          s = true;
        }
        ++m;
        ++c;
      } else if (m != 0 && ch == ':' && c + 1 < cl && qu.charAt(c + 1) == ')') {
        --m;
        ++c;
      } else if (m == 0) {
        if (ch > ' ') sb.append(ch);
        else if (!s) sb.append(' ');
        s = ch <= ' ';
      }
    }
    if (sb.length() >= max) sb.append("...");
    return sb.toString().trim();
  }

  /**
   * Returns a tree representation of the query plan.
   *
   * @return root node
   */
  public FDoc plan() {
    return new FDoc().add(qc.plan());
  }

  @Override
  public String shortInfo() {
    return PLEASE_WAIT_D;
  }

  @Override
  public String detailedInfo() {
    return PLEASE_WAIT_D;
  }

  @Override
  public String toString() {
    return query;
  }
}