org.intermine.webservice.client.lists.ItemList.java Source code

Java tutorial

Introduction

Here is the source code for org.intermine.webservice.client.lists.ItemList.java

Source

package org.intermine.webservice.client.lists;

/*
 * Copyright (C) 2002-2013 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import static org.apache.commons.collections.CollectionUtils.collect;
import static org.apache.commons.collections.TransformerUtils.invokerTransformer;
import static org.apache.commons.collections.TransformerUtils.stringValueTransformer;
import org.intermine.pathquery.Constraints;
import org.intermine.pathquery.PathQuery;
import org.intermine.webservice.client.core.ServiceFactory;
import org.intermine.webservice.client.results.Item;
import org.intermine.webservice.client.services.ListService.ListCreationInfo;
import org.intermine.webservice.client.services.ListService.ListOperationInfo;
import org.intermine.webservice.client.util.PQUtil;

/**
 * A representation of a list of objects stored on the server.
 *
 * The object is not meant to be directly instantiated nor modified. Rather it
 * should be retrieved from the originating web-service, and only modified through
 * the available methods (appending new items, and renaming the list).
 *
 * See {@link InterMineBag}
 * @author Alex Kalderimis
 */
public class ItemList extends AbstractSet<Item> implements Iterable<Item> {

    private final ServiceFactory services;
    private String name;
    private int size;
    private String type;
    private String description;
    private String status;
    private List<String> tags;
    private boolean authorized;
    private Date createdAt;
    private Set<String> unmatchedIds = new HashSet<String>();
    private List<Item> items = null;

    /**
     * Get the identifiers used in creating or appending to this list that
     * could not be resolved to any objects. This set will be updated
     * whenever new requests are made to append to this list.
     * @return A set of unmatched identifiers.
     */
    public Set<String> getUnmatchedIdentifiers() {
        return new HashSet<String>(unmatchedIds);
    }

    /**
     * Get the date when this list was created.
     * @return The list's creation date.
     */
    public Date getCreatedAt() {
        return createdAt;
    }

    /**
     * Add a new unmatched id to the set of unmatched identifiers.
     *
     * This is meant for use by the service modules.
     * @param id The id to add.
     */
    protected void addUnmatchedId(String id) {
        unmatchedIds.add(id);
    }

    /**
     * Add some new unmatched ids to the set of unmatched identifiers.
     *
     * This is meant for use by the service modules.
     * @param ids the ids to add.
     */
    public void addUnmatchedIds(Collection<String> ids) {
        unmatchedIds.addAll(ids);
    }

    /**
     * @return Whether the current user has the authority to modify this list.
     */
    public boolean isAuthorized() {
        return authorized;
    }

    /**
     * Modifying the returned set will not change the underlying tags collection.
     * @return the tags this list has.
     */
    public Set<String> getTags() {
        return new HashSet<String>(tags);
    }

    /**
     * Make sure that the tags on this object are up-to-date with those
     * stored on the server.
     */
    public void updateTags() {
        tags = services.getListService().getTags(this);
    }

    /**
     * Add some new tags to this list. After adding, list will have an
     * updated set of tags, and the tags will be stored on the server.
     * @param newTags The tags to add.
     */
    public void addTags(String... newTags) {
        tags = services.getListService().addTags(this, newTags);
    }

    /**
     * Remove some tags from this list. After removing, the list will have
     * an updated set of tags, and the tags will be synchronised with those on
     * the server.
     * @param removeThese The tags to remove.
     */
    public void removeTags(String... removeThese) {
        tags = services.getListService().removeTags(this, removeThese);
    }

    /**
     * Get the name of this list.
     * @return The list's name.
     */
    public String getName() {
        return name;
    }

    /**
     * @return the number of items in this list.
     */
    public int getSize() {
        return size;
    }

    /**
     * Synonym for getSize()
     * @return the size of the list.
     **/
    @Override
    public int size() {
        return getSize();
    }

    /**
     * @return return the type of the list.
     */
    public String getType() {
        return type;
    }

    /**
     * @return The description of the list, or null.
     */
    public String getDescription() {
        return description;
    }

    /**
     * Any status other than <code>CURRENT</code>
     * indicated this list needs manual attention in the
     * webapp.
     * @return the current status if the list.
     */
    public String getStatus() {
        return status;
    }

    /**
     * Construct the ItemList object.
     *
     * @param fac A reference to the ServiceFactory for the web-service.
     * @param name The name of the list.
     * @param desc The list's description.
     * @param size The number of objects in the list.
     * @param type The class of the objects in the list.
     * @param tags The tags this list is associated with.
     * @param authorized Whether the user can modify this list.
     * @param createdAt When the list was created.
     * @param status The upgrade status of the list.
     */
    ItemList(ServiceFactory fac, String name, String desc, int size, String type, List<String> tags,
            boolean authorized, Date createdAt, String status) {
        assert (name != null);
        assert (type != null);
        this.services = fac;
        this.name = name;
        this.description = desc;
        this.size = size;
        this.type = type;
        this.tags = tags;
        this.authorized = authorized;
        this.createdAt = createdAt;
        this.status = status;
    }

    @Override
    public String toString() {
        return name + " (" + description + " - " + size + " " + type + "s) created at: " + createdAt + ", tags: "
                + tags + ", authorized: " + authorized;
    }

    private List<Item> getItems() {
        if (items == null) {
            items = new ArrayList<Item>();
            for (Item i : this) {
                items.add(i);
            }
        }
        return items;
    }

    /**
     * Get an item by its index. Note that while the order of elements is
     * determinate, it is difficult to predict what that order may be.
     *
     * @param index The index of the element to get.
     * @return an Item representing one of the elements in the list.
     */
    public Item get(int index) {
        return getItems().get(index);
    }

    private void update(ItemList updated) {
        items = null;
        this.size = updated.size();
        this.unmatchedIds.addAll(updated.getUnmatchedIdentifiers());
    }

    /**
     * Add new items to the list by resolving the identifiers of objects.
     *
     * This method will update the size of the list, and clear the items cache.
     * @param ids The identifiers to use to find objects to add.
     */
    public void append(String... ids) {
        update(services.getListService().append(this, ids));
    }

    /**
    * Add new items to the list by resolving the identifiers of objects.
    *
    * This method will update the size of the list, and clear the items cache.
    * @param ids The identifiers to use to find objects to add.
    **/
    public void append(Collection<? extends String> ids) {
        update(services.getListService().append(this, ids.toArray(new String[ids.size()])));
    }

    /**
     * Add new items to the list.
     *
     * This method will update the size of the list, and clear the items cache.
     * @param items The items to add.
     **/
    public void append(Item... items) {
        appendItems(Arrays.asList(items));
    }

    /**
     * Add new items to the list by resolving the identifiers of objects.
     *
     * This method will update the size of the list, and clear the items cache.
     * @param pq The query to run to find objects with.
     **/
    public void append(PathQuery pq) {
        update(services.getListService().append(this, pq));
    }

    @Override
    public boolean add(Item i) {
        return addAll(Arrays.asList(i));
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean addAll(@SuppressWarnings("rawtypes") Collection items) {
        int priorSize = getSize();
        appendItems(items);
        return priorSize != getSize();
    }

    /**
     * Add items to this list by using a query.
     * @param items The items to add to the list.
     */
    private void appendItems(Iterable<Item> items) {
        PathQuery pq = new PathQuery(services.getModel());
        pq.addViews(getType() + ".id");
        Set<String> values = new HashSet<String>();
        for (Item i : items) {
            values.add(Integer.toString(i.getId()));
        }
        pq.addConstraint(Constraints.oneOfValues(getType() + ".id", values));
        update(services.getListService().append(this, pq));
    }

    // -- SUBTRACTION

    @Override
    public boolean remove(Object i) {
        int priorSize = getSize();
        if (i instanceof Item) {
            Item item = (Item) i;
            String path = item.getType() + ".id";
            PathQuery pq = new PathQuery(services.getModel());
            pq.addView(path);
            pq.addConstraint(Constraints.eq(path, Integer.toString(item.getId())));
            createListAndSubtract(pq);
        }
        return priorSize != getSize();
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean removeAll(@SuppressWarnings("rawtypes") Collection c) {
        int priorSize = getSize();
        if (c instanceof ItemList) {
            ItemList res = subtract(((ItemList) c));
            update(res);
            delete();
            res.rename(getName());
        } else if (!c.isEmpty()) {
            try {
                Item[] is = (Item[]) c.toArray(new Item[0]);
                String path = is[0].getType() + ".id";
                PathQuery pq = new PathQuery(services.getModel());
                pq.addView(path);
                pq.addConstraint(Constraints.oneOfValues(path, collect(
                        collect(Arrays.asList(is), invokerTransformer("getId")), stringValueTransformer())));
                createListAndSubtract(pq);
            } catch (ArrayStoreException e) {
                // Do nothing - we didn't get a collection of items.
            }
        }
        return priorSize != getSize();
    }

    private void createListAndSubtract(PathQuery pq) {
        ListCreationInfo info = services.getListService().new ListCreationInfo(pq);
        ItemList removalList = null;
        try {
            removalList = services.getListService().createList(info);
            ItemList res = subtract(removalList);
            update(res);
            delete();
            res.rename(getName());
        } finally {
            try {
                removalList.delete();
            } catch (Exception e) {
                // Ignore
            }
        }
    }

    /**
     * Call for this list to be deleted on the server. As soon as the call returns,
     * this list should no longer be used for any operation.
     */
    public void delete() {
        services.getListService().deleteList(this);
    }

    /**
     * Remove the items in the given lists from this list.
     *
     * This call performs the operation of asymmetric difference on the current list
     * and returns the result. For operations that alter the
     * current list see {@link #removeAll(Collection)}
     * @param lists The lists to subtract from this list.
     * @return A new list
     */
    public ItemList subtract(ItemList... lists) {
        return services.getListService().subtract(Collections.singleton(this), Arrays.asList(lists));
    }

    /**
     * Remove the items in the given lists from this list.
     *
     * This call performs the operation of asymmetric difference on the current list
     * and returns the result. For operations that alter the
     * current list see {@link #removeAll(Collection)}
     * @param lists The lists to subtract from this list.
     * @param info The options describing the new list.
     * @return A new list
     */
    public ItemList subtract(ListOperationInfo info, ItemList... lists) {
        return services.getListService().subtract(info, Collections.singleton(this), Arrays.asList(lists));
    }

    /**
     * Remove the items in the given lists from this list.
     *
     * This call performs the operation of asymmetric difference on the current list
     * and returns the result. For operations that alter the
     * current list see {@link #removeAll(Collection)}
     * @param lists The lists to subtract from this list.
     * @return A new list
     */
    public ItemList subtract(Collection<ItemList> lists) {
        return services.getListService().subtract(Collections.singleton(this), lists);
    }

    /**
     * Remove the items in the given lists from this list.
     *
     * This call performs the operation of asymmetric difference on the current list
     * and returns the result. For operations that alter the
     * current list see {@link #removeAll(Collection)}
     * @param lists The lists to subtract from this list.
     * @param info The options describing the new list.
     * @return A new list
     */
    public ItemList subtract(ListOperationInfo info, Collection<ItemList> lists) {
        return services.getListService().subtract(info, Collections.singleton(this), lists);
    }

    // -- RENAMING

    /**
     * Rename this list on the server.
     *
     * This method will update the name of the list stored on the server, and
     * also change of the name returned by this object.
     * @param newName The new name to give this list.
     */
    public void rename(String newName) {
        ItemList updated = services.getListService().rename(this, newName);
        this.name = updated.getName();
    }

    @Override
    public boolean contains(Object o) {
        if (o == null) {
            return false;
        }
        if (o instanceof Item) {
            Item i = (Item) o;
            return containsAll(Arrays.asList(i));
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean containsAll(@SuppressWarnings("rawtypes") Collection c) {
        if (c == null) {
            return false;
        }
        Item[] candidates;
        try {
            candidates = (Item[]) c.toArray(new Item[c.size()]);
        } catch (ArrayStoreException e) {
            return false;
        }
        return services.getListService().contains(this, candidates);
    }

    /**
     * Convenience method for finding members of a collection matching certain properties.
     *
     * This search is implemented with a query that performs case-insensitive matching on values.
     * Null constraints will be honoured, but other lookups will be converted to strings. Wild-cards
     * are permitted wherever they are permitted in path-queries.
     * <br/>
     * Using a query to back the search means that finding matching members is efficient even over
     * large lists - you will not need to pull in and iterate over thousands of rows of data to find
     * items if you have specific conditions.
     * <br/>
     * These conditions must all match for the result to be included. For more specific and flexible
     * searching strategies, please see the {@link PathQuery} API.
     *
     * @param conditions The properties these elements should have.
     * @return A list of matching items.
     */
    public List<Item> find(Map<String, Object> conditions) {
        List<Item> ret = new ArrayList<Item>();
        PathQuery q = new PathQuery(services.getModel());
        q.addViews(PQUtil.getStar(services.getModel(), type));
        q.addConstraint(Constraints.in(type, name));
        for (Entry<String, Object> condition : conditions.entrySet()) {
            String path = type + "." + condition.getKey();
            if (condition.getValue() == null) {
                q.addConstraint(Constraints.isNull(path));
            } else {
                q.addConstraint(Constraints.eq(path, condition.getValue().toString()));
            }
        }
        List<Map<String, Object>> results = services.getQueryService().getRowsAsMaps(q);
        for (Map<String, Object> result : results) {
            ret.add(new Item(services, type, result));
        }
        return ret;
    }

    @Override
    public Iterator<Item> iterator() {
        PathQuery pq = new PathQuery(services.getModel());
        pq.addViews(PQUtil.getStar(services.getModel(), getType()));
        pq.addConstraint(Constraints.in(type, name));
        Iterator<Map<String, Object>> it = services.getQueryService().getRowMapIterator(pq);
        return new ItemIterator(it);
    }

    private class ItemIterator implements Iterator<Item> {

        Iterator<Map<String, Object>> results;

        ItemIterator(Iterator<Map<String, Object>> results) {
            this.results = results;
        }

        @Override
        public boolean hasNext() {
            return results.hasNext();
        }

        @Override
        public Item next() {
            return new Item(services, type, results.next());
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("removal is not supported");
        }
    }

}