de.uni_koeln.ub.drc.ui.views.SearchView.java Source code

Java tutorial

Introduction

Here is the source code for de.uni_koeln.ub.drc.ui.views.SearchView.java

Source

/**************************************************************************************************
 * Copyright (c) 2010 Fabian Steeg. All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 * <p/>
 * Contributors: Fabian Steeg - initial API and implementation
 *************************************************************************************************/
package de.uni_koeln.ub.drc.ui.views;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.PostConstruct;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.part.ViewPart;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import scala.Enumeration;
import scala.collection.JavaConversions;

import com.quui.sinist.XmlDb;

import de.uni_koeln.ub.drc.data.Index;
import de.uni_koeln.ub.drc.data.Page;
import de.uni_koeln.ub.drc.data.SearchOption;
import de.uni_koeln.ub.drc.data.Status;
import de.uni_koeln.ub.drc.data.Tag;
import de.uni_koeln.ub.drc.data.User;
import de.uni_koeln.ub.drc.data.Word;
import de.uni_koeln.ub.drc.ui.Messages;
import de.uni_koeln.ub.drc.ui.facades.SessionContextSingleton;
import de.uni_koeln.ub.drc.ui.util.ViewPartFinder;
import de.uni_koeln.ub.drc.util.Chapter;
import de.uni_koeln.ub.drc.util.MetsTransformer;

/**
 * View containing a search field and a table viewer displaying pages.
 * 
 * @author Fabian Steeg (fsteeg), Mihail Atanassov (matana)
 */
public final class SearchView extends ViewPart {

    /**
     * The class / SearchView ID
     */
    public static final String ID = SearchView.class.getName().toLowerCase();

    private Text searchField;
    private Text tagField;
    private Label resultCount;
    private TreeViewer viewer;
    private Combo searchOptions;
    private TreeMap<String, Enumeration.Value> options = new TreeMap<String, Enumeration.Value>();
    private Combo volumes;
    private Composite searchComposite;
    private Button close;
    private List<String> allPages;
    private int index;
    private Label currentPageLabel;
    private List<Page> toExport;
    private String selected;

    // private User currentUser;

    /**
     * @param parent
     *            The parent composite for this part
     */
    @Override
    public void createPartControl(Composite parent) {
        getSite().setSelectionProvider(viewer);
        searchComposite = new Composite(parent, SWT.NONE);
        searchComposite.setLayout(new GridLayout(7, false));
        initVolumeSelector(searchComposite);
        initSearchField(searchComposite);
        initOptionsCombo(searchComposite);
        initTableViewer(parent);
        addPageInfoBar(parent);
        GridLayoutFactory.fillDefaults().generateLayout(parent);
        setInput();
        select();
    }

    @Override
    public void setFocus() {
    }

    /**
     * @return The selection to export
     */
    public List<Page> getSelectedPages() {
        return toExport;
    }

    User getUser() {
        // final Display display = Display.getCurrent();
        // final Runnable runnable = new Runnable() {
        // @Override
        // public void run() {
        // UICallBack.runNonUIThreadWithFakeContext(display,
        // new Runnable() {
        // @Override
        // public void run() {
        // currentUser = SessionContextSingleton
        // .getInstance().getCurrentUser();
        // }
        // });
        // }
        // };
        // new Thread(runnable).start();
        // ILoginContext attribute = (ILoginContext) RWT.getSessionStore()
        //            .getAttribute("loginContext"); //$NON-NLS-1$
        // User user = null;
        // try {
        // System.out.println(SessionContextSingleton.getInstance()
        // .fetchSingletonInfo());
        // user = (User) attribute.getSubject().getPrivateCredentials()
        // .iterator().next();
        // } catch (LoginException e) {
        // e.printStackTrace();
        // }
        return SessionContextSingleton.getInstance().getCurrentUser();
        // return currentUser;
    }

    /**
     * Select the last word edited by this user.
     */
    public void select() {

        String latestPage = getUser().latestPage();
        if (latestPage.trim().isEmpty()) {
            return;
        }
        String volume = latestPage.split("_")[1].split("-")[0]; //$NON-NLS-1$ //$NON-NLS-2$
        volumes.select(Index.RF().indexOf(volume));
        setInput();
        if (viewer.getTree().getItems().length == 0) {
            throw new IllegalArgumentException(Messages.get().NoEntries);
        }
        allPages = new ArrayList<String>(JavaConversions.asJavaList(content.modelIndex.pages()));
        Collections.sort(allPages);
        for (String pageId : allPages) {
            if (pageId.equals(latestPage)) {
                select(pageId);
                break;
            }
        }
        if (viewer.getSelection().isEmpty()) {
            viewer.setSelection(new StructuredSelection(allPages.get(0)));
        }

    }

    /**
     * Updates the TreeViewer after a Word has been modified and saved.
     */
    public void updateTreeViewer() {
        viewer.setLabelProvider(new SearchViewLabelProvider());
    }

    private Comparator<Object> comp = new Comparator<Object>() {
        @Override
        public int compare(Object p1, Object p2) {
            if (p1 instanceof Page && p2 instanceof Page)
                return ((Page) p1).id().compareTo(((Page) p2).id());
            return p1.toString().compareTo(p2.toString());
        }
    };

    private Combo show;

    private void initVolumeSelector(Composite searchComposite) {
        Label label1 = new Label(searchComposite, SWT.NONE);
        label1.setText(Messages.get().Volume);
        volumes = new Combo(searchComposite, SWT.READ_ONLY);
        String[] volumeLabels = new String[Index.RF().size()];
        for (int i = 0; i < Index.RF().size(); i++) {
            volumeLabels[i] = Index.Volumes().get(Index.RF().apply(i)).get();
        }
        volumes.setItems(volumeLabels);
        volumes.setData(JavaConversions.asJavaList(Index.RF()));
        volumes.select(0);
        volumes.addSelectionListener(searchListener);
        Label label2 = new Label(searchComposite, SWT.NONE);
        label2.setText(Messages.get().Has);
    }

    /* DI */@SuppressWarnings("unused")
    @PostConstruct
    private void addFocusListener() {
        searchField.addFocusListener(new SpecialCharacterView.TextFocusListener(searchField));
        tagField.addFocusListener(new SpecialCharacterView.TextFocusListener(tagField));
    }

    private enum Navigate {
        NEXT, PREV
    }

    private class NavigationListener implements SelectionListener {
        private Navigate nav;

        public NavigationListener(Navigate nav) {
            this.nav = nav;
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            viewer.setSelection(new StructuredSelection());
            index = nav == Navigate.PREV ? index - 1 : index + 1;
            busyCursorWhile(viewer.getControl().getDisplay(), new Runnable() {
                @Override
                public void run() {
                    updateSelection();
                }
            });
        }

        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
        }
    }

    private void addPageInfoBar(Composite parent) {
        Composite bottomComposite = new Composite(parent, SWT.NONE);
        bottomComposite.setLayout(new GridLayout(9, false));
        Button prev = new Button(bottomComposite, SWT.PUSH | SWT.FLAT);
        prev.setImage(SessionContextSingleton.getInstance().loadImage("icons/prev.gif")); //$NON-NLS-1$
        prev.addSelectionListener(new NavigationListener(Navigate.PREV));
        Button next = new Button(bottomComposite, SWT.PUSH | SWT.FLAT);
        next.setImage(SessionContextSingleton.getInstance().loadImage("icons/next.gif")); //$NON-NLS-1$
        next.addSelectionListener(new NavigationListener(Navigate.NEXT));
        currentPageLabel = new Label(bottomComposite, SWT.NONE);
        Label label = new Label(bottomComposite, SWT.NONE);
        label.setText(Messages.get().Show + ": "); //$NON-NLS-1$
        show = new Combo(bottomComposite, SWT.READ_ONLY);
        show.setItems(new String[] { Messages.get().All, Messages.get().Open });
        show.select(0);
        show.addSelectionListener(searchListener);
        insertAddTagButton(bottomComposite);
        currentPageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        addPageCheckedButton(bottomComposite);
    }

    private void addPageCheckedButton(Composite parent) {
        close = new Button(parent, SWT.CHECK);
        close.setText(Messages.get().ClosePage);
        close.addListener(SWT.MouseUp, new Listener() {
            @Override
            public void handleEvent(org.eclipse.swt.widgets.Event event) {
                final Page page = page(allPages.get(index));
                page.status()
                        .$plus$eq(new Status(getUser().id(), System.currentTimeMillis(), close.getSelection()));
                page.saveToDb(getUser().collection(), SessionContextSingleton.getInstance().db());
                viewer.setLabelProvider(new SearchViewLabelProvider());
                reload(close.getParent(), page);
            }
        });
    }

    private void insertAddTagButton(Composite bottomComposite) {
        Label label = new Label(bottomComposite, SWT.NONE);
        label.setText(Messages.get().AddTag);
        tagField = new Text(bottomComposite, SWT.BORDER);
        Button addComment = new Button(bottomComposite, SWT.PUSH | SWT.FLAT);
        addComment.setToolTipText(Messages.get().AddNewTagToCurrentPage);
        addComment.setImage(SessionContextSingleton.getInstance().loadImage("icons/add.gif")); //$NON-NLS-1$
        SelectionListener listener = new SelectionListener() {
            @Override
            // on button click
            public void widgetSelected(SelectionEvent e) {
                addTag(tagField);
            }

            @Override
            // on enter in text
            public void widgetDefaultSelected(SelectionEvent e) {
                addTag(tagField);
            }

            private void addTag(final Text text) {
                final String input = text.getText();
                if (input != null && input.trim().length() != 0) {
                    Page page = page(allPages.get(index));
                    page.tags().$plus$eq(new Tag(input, getUser().id()));
                    page.saveToDb(getUser().collection(), SessionContextSingleton.getInstance().db());
                    setCurrentPageLabel(page);
                    viewer.setLabelProvider(new SearchViewLabelProvider());
                    text.setText(""); //$NON-NLS-1$   
                }
            }
        };
        addComment.addSelectionListener(listener);
        tagField.addSelectionListener(listener);
        // FIXME: Insertion of special characters
        // tagField.addFocusListener(new
        // SpecialCharacterView.TextFocusListener(tagField));
    }

    private Page page(String string) {
        volumes.getDisplay().syncExec(new Runnable() {
            @Override
            public void run() {
                selected = selected(volumes);
            }
        });

        return Page.fromXml(SessionContextSingleton.getInstance().db()
                .getXml(getUser().collection() + "/" + selected, //$NON-NLS-1$
                        JavaConversions.asScalaBuffer(Arrays.asList(string)))
                .get().head(), ""); //$NON-NLS-1$
    }

    private void updateSelection() {
        if (index < allPages.size() && index >= 0) {
            Page page = page(allPages.get(index));
            setCurrentPageLabel(page);
            StructuredSelection selection = new StructuredSelection(new Page[] { page });
            viewer.setSelection(selection);
            reload(viewer.getTree().getParent(), page);
        }
    }

    private void reload(final Composite parent, final Page page) {
        if (viewer.getSelection() instanceof List && ((List<?>) viewer.getSelection()).size() == 1) {
            System.out.println(Messages.get().ReloadingPage + page);
            parent.getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    Page reloaded = page(page.id());
                    viewer.setSelection(new StructuredSelection(reloaded));
                }
            });
        }
    }

    private void setCurrentPageLabel(Page page) {
        // currentPageLabel
        // .setText(String.format(
        // Messages.get().CurrentPageVolume
        //                        + " %s, " + Messages.get().Page + " %s", volumes.getItem(volumes.getSelectionIndex()), //$NON-NLS-1$ //$NON-NLS-2$
        // mets != null ? mets.label(page.number()) /**/
        // : page.number()));// no meta: use number as label
        // close.setSelection(page.done());
        currentPageLabel
                .setText(String.format(Messages.get().CurrentPageVolume + " %s, " + Messages.get().Page + " %s", //$NON-NLS-1$//$NON-NLS-2$
                        volumes.getItem(volumes.getSelectionIndex()), physMap.get(page.id())));
        close.setSelection(page.done());
    }

    private void select(String pageId) {
        Page page = page(pageId);
        Chapter chapter = new Chapter(0, 0, ""); //$NON-NLS-1$
        if (chapters != null) {
            boolean flag = false;
            for (Chapter c : chapters.keySet()) {
                List<Object> list = chapters.get(c);
                for (Object o : list) {
                    String id = o instanceof Page ? ((Page) o).id() : new Page(null, (String) o).id();
                    if (pageId.equals(id)) {
                        chapter = c;
                        flag = true;
                        break;
                    }
                }
                if (flag)
                    break;
            }
        }

        // Chapter chapter = mets.chapters(page.number(), Count.File()).head();
        TreeItem[] items = viewer.getTree().getItems();
        for (TreeItem treeItem : items) {
            if (treeItem.getText(3).contains(chapter.title())) {
                treeItem.setExpanded(true);
                break;
            }
        }
        viewer.refresh(chapter);
        viewer.setSelection(new StructuredSelection(page));
        EditView view = ViewPartFinder.find(EditView.class);
        view.focusLatestWord();
    }

    private void initSearchField(final Composite parent) {
        resultCount = new Label(parent, SWT.NONE);
        updateResultCount(-1);
        searchField = new Text(parent, SWT.BORDER);
        searchField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        searchField.addSelectionListener(searchListener);
    }

    private void initOptionsCombo(final Composite searchComposite) {
        Label label = new Label(searchComposite, SWT.NONE);
        label.setText(Messages.get().In);
        options.put(Messages.get().Text, SearchOption.all());
        options.put(Messages.get().Tags, SearchOption.tags());
        options.put(Messages.get().Comments, SearchOption.comments());
        searchOptions = new Combo(searchComposite, SWT.READ_ONLY);
        searchOptions.setItems(options.keySet().toArray(new String[options.keySet().size()]));
        searchOptions.select(new ArrayList<String>(options.keySet()).indexOf(Messages.get().Text));
        searchOptions.addSelectionListener(searchListener);
    }

    private SelectionListener searchListener = new SelectionListener() {
        @Override
        public void widgetSelected(final SelectionEvent e) {
            setInput();
        }

        @Override
        public void widgetDefaultSelected(final SelectionEvent e) {
            setInput();
        }
    };

    private void updateResultCount(int count) {
        resultCount.setText(String.format("%s %s " + Messages.get().For, count == -1 ? "[no]" : count, //$NON-NLS-1$//$NON-NLS-2$
                count == 1 ? Messages.get().Hit : Messages.get().Hits));
    }

    private void initTableViewer(final Composite parent) {
        viewer = new TreeViewer(parent, SWT.MULTI | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                busyCursorWhile(viewer.getControl().getDisplay(), new Runnable() {
                    @SuppressWarnings("unchecked")
                    @Override
                    public void run() {
                        final IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                        Object item = selection.getFirstElement();
                        if (item instanceof Page) {
                            final Page page = (Page) item;
                            toExport = selection.toList();
                            index = allPages.indexOf(page.id());
                            setCurrentPageLabel(page);
                        }
                    }
                });
            }
        });
        initTable();
        viewer.setContentProvider(new SearchViewContentProvider());
        viewer.setLabelProvider(new SearchViewLabelProvider());
        getSite().setSelectionProvider(viewer);
        // FIXME Select last edit page
        // DrcUiActivator.getDefault().register(this);
        // setInput();
        // select();
    }

    private void busyCursorWhile(final Display display, final Runnable runnable) {
        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                BusyIndicator.showWhile(viewer.getControl().getDisplay(), runnable);
            }
        });
    }

    private void initTable() {
        final int[] columns = new int[] { 60, 50, 60, 400, 200, 150, 100 };
        createColumn("", columns[0]); //$NON-NLS-1$
        createColumn(Messages.get().Volume, columns[1]);
        createColumn(Messages.get().Page, columns[2]);
        createColumn(Messages.get().Text, columns[3]);
        createColumn(Messages.get().Modified, columns[4]);
        createColumn(Messages.get().Tags, columns[5]);
        createColumn(Messages.get().Comments, columns[6]);
        Tree tree = viewer.getTree();
        tree.setHeaderVisible(true);
        tree.setLinesVisible(true);
    }

    private void createColumn(final String name, final int width) {
        TreeViewerColumn column1 = new TreeViewerColumn(viewer, SWT.NONE);
        column1.getColumn().setText(name);
        column1.getColumn().setWidth(width);
        column1.getColumn().setResizable(true);
        column1.getColumn().setMoveable(true);
    }

    private Map<Chapter, List<Object>> chapters = new TreeMap<Chapter, List<Object>>();
    private MetsTransformer mets;
    private String last = JavaConversions.asJavaList(Index.RF()).get(0);

    /**
     * Load SearchView content
     */
    public void setInput() {
        BusyIndicator.showWhile(viewer.getControl().getDisplay(), new Runnable() {
            @Override
            public void run() {
                load();
            }
        });
    }

    private void pingCollection(final String selectedVolume, final Page page, final XmlDb db) {
        /* Ping the collection in the background to avoid delay on first save: */
        new Thread(new Runnable() {
            @Override
            public void run() {
                db.putXml(page.toXml(), Index.DefaultCollection() + "/" + selectedVolume, page.id()); //$NON-NLS-1$
            }
        }).start();
    }

    private void loadData() {
        content = new SearchViewModelProvider();
    }

    private SearchViewModelProvider content = null;

    private Map<String, String> physMap;

    private final class SearchViewModelProvider {

        Index modelIndex;
        String modelSelected = null;

        private SearchViewModelProvider() {
            viewer.getTree().getDisplay().syncExec(new Runnable() {
                @Override
                public void run() {
                    modelSelected = selected(volumes);
                }
            });
            List<String> ids = JavaConversions.asJavaList(SessionContextSingleton.getInstance().db()
                    .getIds(getUser().collection() + "/" + modelSelected).get()); //$NON-NLS-1$
            // we only load the XML files
            List<String> pages = new ArrayList<String>();
            for (String id : ids) {
                if (id.endsWith(".xml")) { //$NON-NLS-1$
                    pages.add(id);
                }
            }
            modelIndex = new Index(JavaConversions.asScalaBuffer(pages).toList(),
                    SessionContextSingleton.getInstance().db(), modelSelected, Index.DefaultCollection());
        }

        Object[] search;

        public Object[] getPages(final String term) {
            final String type = searchOptions.getItem(searchOptions.getSelectionIndex());
            if (term.trim().equals("")) { //$NON-NLS-1$
                search = JavaConversions.asJavaList(modelIndex.pages())
                        .toArray(new String[modelIndex.pages().size()]);
            } else {
                search = null;
                search = modelIndex.search(term, options.get(type));
                System.out.println(String.format("Searching for '%s' in %s returned %s results", //$NON-NLS-1$
                        term, type, search.length));
            }
            return search;
        }
    }

    private final class SearchViewContentProvider implements ITreeContentProvider {
        @Override
        public Object[] getElements(final Object inputElement) {
            if (inputElement instanceof Map) {
                Object[] array = ((Map<?, ?>) inputElement).keySet().toArray(new Chapter[] {});
                Arrays.sort(array);
                return array;
            }
            Object[] elements = (Object[]) inputElement;
            return elements;
        }

        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            List<Object> ids = chapters.get(parentElement);
            List<Page> pages = new ArrayList<Page>();
            for (Object object : ids) {
                Page page = object instanceof String ? page((String) object) : (Page) object;
                if (show.getSelectionIndex() == 0 || !page.done()) {
                    pages.add(page);
                }
            }
            return pages.toArray(new Page[] {});
        }

        @Override
        public Object getParent(Object element) {
            return null;
        }

        @Override
        public boolean hasChildren(Object element) {
            return element instanceof Chapter;
        }
    }

    private boolean isPage(Object element) {
        return element instanceof Page;
    }

    @SuppressWarnings("unchecked")
    private String selected(Combo volumes) {
        return "PPN345572629_" //$NON-NLS-1$
                + (volumes == null ? JavaConversions.asJavaList(Index.RF()).get(0)
                        : ((List<String>) volumes.getData()).get(volumes.getSelectionIndex()));
    }

    private Page asPage(Object element) {
        return (Page) element;
    }

    private void load() {
        String selectedVolume = selected(volumes);
        XmlDb db = SessionContextSingleton.getInstance().db();
        if (content == null || !selectedVolume.equals(last)) {
            loadData();
            allPages = new ArrayList<String>(JavaConversions.asJavaList(content.modelIndex.pages()));
            pingCollection(selectedVolume, page(allPages.get(0)), db);
            Collections.sort(allPages);
        }
        last = selectedVolume;
        Object[] pages = content.getPages(searchField.getText().trim().toLowerCase());
        Arrays.sort(pages, comp);
        chapters = new TreeMap<Chapter, List<Object>>();

        // boolean meta = true;
        // try {
        //         mets = new MetsTransformer(selectedVolume + ".xml", db); //$NON-NLS-1$
        // } catch (NullPointerException x) {
        // // No metadata available for selected volume
        // meta = false;
        // mets = null; // prevent using existing mets
        // }
        // for (Object page : pages) {
        // int fileNumber = page instanceof Page ? ((Page) page).number()
        // : new Page(null, (String) page).number();
        // List<Chapter> chaptersForPage = meta ? /**/
        // JavaConversions.asJavaList(mets.chapters(fileNumber,
        // Count.File()))
        // : Arrays.asList(new Chapter(0, 1, Messages.get().NoMeta));
        // for (Chapter chapter : chaptersForPage) {
        // List<Object> pagesInChapter = chapters.get(chapter);
        // if (pagesInChapter == null) {
        // pagesInChapter = new ArrayList<Object>();
        // chapters.put(chapter, pagesInChapter);
        // }
        // pagesInChapter.add(page);
        // }
        // }

        // Use if no compressed meta data is available
        // generateXML();

        try {
            chapters = getChapters(selectedVolume, pages);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        viewer.setInput(chapters);
        updateResultCount(pages.length);
    }

    private Map<Chapter, List<Object>> getChapters(final String selectedVolume, final Object[] pages)
            throws ParserConfigurationException, SAXException, IOException {
        // use pageIDs for filtering by search result
        ArrayList<String> pageIDs = grabCurrentPages(pages);
        Map<Chapter, List<Object>> chapters = new TreeMap<Chapter, List<Object>>();
        physMap = new TreeMap<String, String>();
        NodeList nodeList = getNodeList(selectedVolume);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                String chapterTitle = element.getAttribute(XmlAttributes.Title.toString().toLowerCase());
                int chapterNumber = Integer
                        .parseInt(element.getAttribute(XmlAttributes.Number.toString().toLowerCase()));
                String s = selectedVolume.substring(13, selectedVolume.length()).replaceAll("[^\\d]", "");//$NON-NLS-1$ //$NON-NLS-2$
                Chapter c = new Chapter(Integer.parseInt(s), chapterNumber, chapterTitle);
                NodeList childNodes = element.getChildNodes();
                List<Object> list = new ArrayList<Object>();
                for (int j = 0; j < childNodes.getLength(); j++) {
                    Node child = childNodes.item(j);
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        Element e = (Element) child;
                        String pageID = e.getAttribute(XmlAttributes.Id.toString().toLowerCase());
                        String physID = e.getAttribute(XmlAttributes.PhysId.toString().toLowerCase());
                        physMap.put(pageID, physID);
                        if (pageIDs.contains(pageID)) // filter by search result
                            list.add(pageID);
                    }
                    if (!list.isEmpty()) // non-empty chapters only
                        chapters.put(c, list);
                }
            }
        }
        return chapters;
    }

    private ArrayList<String> grabCurrentPages(final Object[] pages) {
        ArrayList<String> pageIDs = new ArrayList<String>();
        for (Object page : pages) {
            pageIDs.add(page instanceof Page ? ((Page) page).id() : new Page(null, (String) page).id());
        }
        return pageIDs;
    }

    enum XmlAttributes {
        Chapter, Id, Number, Page, PhysId, Title, Volume
    }

    private InputStream getInputStream(String selectedVolume) {
        InputStream openStream = null;
        try {
            XmlDb db = SessionContextSingleton.getInstance().db();
            URL url = new URL(db.restRoot() + "drc/drc-meta-comp/" //$NON-NLS-1$
                    + selectedVolume + ".xml");//$NON-NLS-1$
            openStream = url.openStream();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return openStream;
    }

    @SuppressWarnings("unused")
    private void generateXML() {
        Document doc = getDocument();
        String volume = last;
        Element volumeElement = createVolumeElement(doc, volume);
        Set<Chapter> keySet = chapters.keySet();
        for (Chapter chapter : keySet) {
            Element chapterElement = createChapterElement(doc, volumeElement, chapter);
            List<Object> list = chapters.get(chapter);
            for (Object object : list) {
                String id = object instanceof Page ? ((Page) object).id() : new Page(null, (String) object).id();
                int number = object instanceof Page ? ((Page) object).number()
                        : new Page(null, (String) object).number();
                createPageElement(doc, chapterElement, id, number);
            }
        }
        writeToXML(doc, volume);
    }

    private void writeToXML(final Document doc, final String volume) {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer;
        try {
            transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(new File(
                    //"C:\\" + volume + ".xml")); //$NON-NLS-1$ //$NON-NLS-2$
                    "~/drc-meta/" + volume + ".xml")); //$NON-NLS-1$ //$NON-NLS-2$
            transformer.transform(source, result);
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
    }

    private void createPageElement(final Document doc, final Element chapterElement, final String pageID,
            final int number) {
        Element pageElement = doc.createElement(XmlAttributes.Id.toString().toLowerCase());
        pageElement.setAttribute(XmlAttributes.Id.toString().toLowerCase(), pageID);
        // no meta available: use number as page label
        pageElement.setAttribute(XmlAttributes.PhysId.toString().toLowerCase(),
                (mets != null ? mets.label(number) : String.valueOf(number)));
        chapterElement.appendChild(pageElement);
    }

    private Element createChapterElement(final Document doc, final Element volumeElement, final Chapter chapter) {
        Element chapterElement = doc.createElement(XmlAttributes.Chapter.toString().toLowerCase());
        volumeElement.appendChild(chapterElement);
        chapterElement.setAttribute(XmlAttributes.Title.toString().toLowerCase(), chapter.title());
        chapterElement.setAttribute(XmlAttributes.Number.toString().toLowerCase(),
                String.valueOf(chapter.number()));
        return chapterElement;
    }

    private Element createVolumeElement(final Document doc, final String title) {
        Element volume = doc.createElement(XmlAttributes.Volume.toString().toLowerCase());
        doc.appendChild(volume);
        volume.setAttribute(XmlAttributes.Title.toString().toLowerCase(), title);
        return volume;
    }

    private Document getDocument() {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder;
        Document doc = null;
        try {
            docBuilder = docFactory.newDocumentBuilder();
            doc = docBuilder.newDocument();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        return doc;
    }

    private NodeList getNodeList(final String selectedVolume)
            throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(getInputStream(selectedVolume));
        doc.getDocumentElement().normalize();
        NodeList nodeList = doc.getElementsByTagName(XmlAttributes.Chapter.toString().toLowerCase());
        return nodeList;
    }

    private final class SearchViewLabelProvider extends LabelProvider implements ITableLabelProvider {
        @Override
        public String getColumnText(final Object element, final int columnIndex) {
            switch (columnIndex) {
            case 0:
                return ""; //$NON-NLS-1$
            case 1:
                return isPage(element) ? volumes.getItem(volumes.getSelectionIndex()) : ""; //$NON-NLS-1$
            case 2:
                // if (mets == null)// no metadata available (e.g. vol. 0014_RC)
                //               return isPage(element) ? ((Page) element).number() + "" : ""; //$NON-NLS-1$ //$NON-NLS-2$
                // return isPage(element) && mets != null ? mets.label(asPage(
                // element).number()) + "" : ""; //$NON-NLS-1$ //$NON-NLS-2$
                return isPage(element) ? physMap.get(((Page) element).id()) : ""; //$NON-NLS-1$
            case 3: {
                if (isPage(element)) {
                    String text = asPage(element).toText("|"); //$NON-NLS-1$
                    return "Text: " //$NON-NLS-1$
                            + text.substring(0, Math.min(60, text.length())) + "..."; //$NON-NLS-1$
                }
                return element.toString();
            }
            case 4:
                return isPage(element) ? lastModificationDate(JavaConversions.asJavaList(asPage(element).words()))
                        : ""; //$NON-NLS-1$
            case 5:
                return isPage(element) ? asPage(element).tags().mkString(", ") : ""; //$NON-NLS-1$ //$NON-NLS-2$
            case 6:
                return isPage(element) ? asPage(element).comments().size() + "" : ""; //$NON-NLS-1$ //$NON-NLS-2$
            default:
                return element.toString();
            }
        }

        private String lastModificationDate(List<Word> words) {
            long latest = 0;
            for (Word word : words) {
                latest = Math.max(latest, word.history().top().date());
            }
            return new Date(latest).toString();
        }

        @Override
        public Image getColumnImage(final Object element, final int columnIndex) {
            if (columnIndex == 0 && element instanceof Page) {
                Page page = (Page) element;
                return SessionContextSingleton.getInstance().loadImage(page.done() ? "icons/complete_status.gif" //$NON-NLS-1$
                        : page.edits() == 0 ? "icons/page.gif" : "icons/edited.gif"); //$NON-NLS-1$ //$NON-NLS-2$
            }
            return null;
        }
    }

}