All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.util.IndexPropertyList Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 ****************************************************************/

package org.apache.cayenne.util;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.ValueHolder;
import org.apache.cayenne.property.PropertyUtils;

/**
 * A List implementation that would maintain its internal ordering based on some object
 * numeric "index" property. When objects are added to the list at a certain index, an
 * "index" property is modified to reflect list order, when objects are removed, their
 * index property is set to the negative number.
 * 

* For performance reasons this implementation does not guarantee that there is no gaps in * the integer ordering sequence (i.e. generally * object.getIndexProperty() != list.indexOf(object)). However it * guarantees the right ordering based on index property. * * @since 1.2 * @author Andrus Adamchik */ public class IndexPropertyList extends AbstractList implements ValueHolder { /** * A default gap maintained between elements index property values. Gaps bigger than 1 * ensure faster and less intrusive additions and removals. */ static final int DEFAULT_GAP = 3; /** * A list used for the actual objects storage. */ protected List list; protected String indexProperty; boolean dirty; Comparator comparator; /** * Creates an empty NumericPropertyOrderedList. */ public IndexPropertyList(String indexProperty) { this(indexProperty, new ArrayList(), false); } /** * Creates a NumericPropertyOrderedList that decorates another list. If the list is * not known to be properly sorted, caller must set sortNeeded to true. * This will result in sorting the original list on first access attempt. */ public IndexPropertyList(String indexProperty, List objects, boolean sortNeeded) { if (indexProperty == null) { throw new IllegalArgumentException("Null sortProperty"); } if (objects == null) { throw new IllegalArgumentException("Null objects list."); } this.indexProperty = indexProperty; this.list = objects; // be lazy - don't sort here, as (a) it may never be needed and (b) a list can be // a Cayenne fault, so resolving it too early is undesirable this.dirty = sortNeeded; } ValueHolder getWrappedValueHolder() { return (list instanceof ValueHolder) ? (ValueHolder) list : null; } public boolean isFault() { ValueHolder h = getWrappedValueHolder(); return (h != null) ? h.isFault() : false; } public Object setValueDirectly(Object value) throws CayenneRuntimeException { ValueHolder h = getWrappedValueHolder(); return h != null ? h.setValueDirectly(value) : null; } public Object setValue(Object value) throws CayenneRuntimeException { ValueHolder h = getWrappedValueHolder(); return h != null ? h.setValue(value) : null; } public Object getValue() throws CayenneRuntimeException { ValueHolder h = getWrappedValueHolder(); return h != null ? h.getValue() : null; } public Object getValueDirectly() throws CayenneRuntimeException { ValueHolder h = getWrappedValueHolder(); return h != null ? h.getValueDirectly() : null; } public void invalidate() { ValueHolder h = getWrappedValueHolder(); if (h != null) { h.invalidate(); } } /** * Changes list state to "dirty" forcing reordering on next access. */ public void touch() { this.dirty = true; } public Object get(int index) { if (dirty) { sort(); } return list.get(index); } public int size() { if (dirty) { sort(); } return list.size(); } public Object set(int index, Object element) { if (dirty) { sort(); } Object removed = list.set(index, element); if (element != null) { int indexValue = (removed != null) ? getIndexValue(removed) : calculateIndexValue(index); setIndexValue(element, indexValue); shift(index + 1, indexValue); } if (removed != null && removed != element) { setIndexValue(removed, -1); } return removed; } public void add(int index, Object element) { if (dirty) { sort(); } list.add(index, element); if (element != null) { int indexValue = calculateIndexValue(index); setIndexValue(element, indexValue); shift(index + 1, indexValue); } } public Object remove(int index) { if (dirty) { sort(); } Object removed = list.remove(index); if (removed != null) { setIndexValue(removed, -1); } return removed; } // ============================================ // ***** Methods to maintain ordering ****** // ============================================ /** * Calculates an index value at the specified list index. Note that using this value * may require a shift of the objects following this index. */ protected int calculateIndexValue(int listIndex) { if (list.isEmpty()) { throw new ArrayIndexOutOfBoundsException(listIndex); } if (list.size() == 1 && listIndex == 0) { return 0; } // handle lists with teo or more elements... // last element if (listIndex == list.size() - 1) { return getIndexValue(get(listIndex - 1)) + DEFAULT_GAP; } int from = (listIndex == 0) ? -1 : getIndexValue(get(listIndex - 1)); int to = getIndexValue(get(listIndex + 1)); return (to - from > 1) ? (to - from) / 2 + from : from + DEFAULT_GAP; } protected int getIndexValue(Object object) { Number n = (Number) PropertyUtils.getProperty(object, indexProperty); if (n == null) { throw new CayenneRuntimeException("Null index property '" + indexProperty + "' for object " + object); } return n.intValue(); } protected void setIndexValue(Object object, int index) { PropertyUtils.setProperty(object, indexProperty, new Integer(index)); } protected void shift(int startIndex, int afterIndexValue) { int size = size(); for (int i = startIndex; i < size; i++) { Object object = get(i); int indexValue = getIndexValue(object); if (indexValue > afterIndexValue) { break; } int newValue = calculateIndexValue(i); setIndexValue(object, newValue); afterIndexValue = newValue; } } /** * Sorts internal list. */ protected void sort() { if (!dirty) { return; } // do not directly sort Cayenne lists, sort the underlying list instead to avoid a // bunch of additions/removals Collections.sort(unwrapList(), getComparator()); dirty = false; } List unwrapList() { if (list instanceof PersistentObjectList) { return ((PersistentObjectList) list).resolvedObjectList(); } else { return list; } } /** * Returns a property Comaparator, creating it on demand. */ Comparator getComparator() { if (comparator == null) { comparator = new Comparator() { public int compare(Object o1, Object o2) { if ((o1 == null && o2 == null) || o1 == o2) { return 0; } else if (o1 == null && o2 != null) { return -1; } else if (o1 != null && o2 == null) { return 1; } Comparable p1 = (Comparable) PropertyUtils.getProperty( o1, indexProperty); Comparable p2 = (Comparable) PropertyUtils.getProperty( o2, indexProperty); return (p1 == null) ? -1 : p1.compareTo(p2); } }; } return comparator; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy