001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012
013package org.nuxeo.ecm.core.storage.sql;
014
015import java.io.Serializable;
016import java.util.Collections;
017import java.util.Comparator;
018import java.util.LinkedList;
019import java.util.List;
020
021/**
022 * A type of fragment corresponding to a single row in a table and its associated in-memory information (state, dirty
023 * fields, attached context).
024 */
025public final class SimpleFragment extends Fragment {
026
027    private static final long serialVersionUID = 1L;
028
029    private static final Row UNKNOWN_ROW = new Row(null, (Serializable) null);
030
031    public static final SimpleFragment UNKNOWN = new SimpleFragment(UNKNOWN_ROW, State.DETACHED, null);
032
033    /**
034     * Constructs a {@link SimpleFragment} from a {@link Row}.
035     *
036     * @param row the row, or {@code null}
037     * @param state the initial state for the fragment
038     * @param context the persistence context to which the fragment is tied, or {@code null}
039     */
040    public SimpleFragment(Row row, State state, PersistenceContext context) {
041        super(row, state, context);
042    }
043
044    @Override
045    protected State refetch() {
046        Row newrow = context.mapper.readSimpleRow(row);
047        if (newrow == null) {
048            row.size = 0;
049            return State.ABSENT;
050        } else {
051            row = newrow;
052            clearDirty();
053            return State.PRISTINE;
054        }
055    }
056
057    @Override
058    protected State refetchDeleted() {
059        row.size = 0;
060        return State.ABSENT;
061    }
062
063    /**
064     * Gets a value by key.
065     *
066     * @param key the key
067     * @return the value
068     */
069    public Serializable get(String key) {
070        accessed();
071        return row.get(key);
072    }
073
074    /**
075     * Puts a value by key.
076     *
077     * @param key the key
078     * @param value the value
079     */
080    public void put(String key, Serializable value) {
081        accessed(); // maybe refetch other values
082        row.put(key, value);
083        // resize olddata to follow row if needed
084        if (oldvalues.length < row.values.length) {
085            Serializable[] tmp = oldvalues;
086            oldvalues = new Serializable[row.values.length];
087            System.arraycopy(tmp, 0, oldvalues, 0, tmp.length);
088        }
089        if (getState() != State.ABSENT || value != null) {
090            // don't mark modified when setting null in an absent fragment
091            // to avoid creating unneeded rows
092            markModified();
093        }
094    }
095
096    /**
097     * Returns a {@code String} value.
098     *
099     * @param key the key
100     * @return the value as a {@code String}
101     * @throws ClassCastException if the value is not a {@code String}
102     */
103    public String getString(String key) {
104        return (String) get(key);
105    }
106
107    /**
108     * Gets the dirty keys (keys of values changed since last clear).
109     *
110     * @return the dirty keys
111     */
112    public List<String> getDirtyKeys() {
113        List<String> keys = null;
114        for (int i = 0; i < row.size; i++) {
115            if (!same(oldvalues[i], row.values[i])) {
116                if (keys == null) {
117                    keys = new LinkedList<String>();
118                }
119                keys.add(row.keys[i]);
120            }
121        }
122        return keys == null ? Collections.<String> emptyList() : keys;
123    }
124
125    private static boolean same(Object a, Object b) {
126        if (a == null) {
127            return b == null;
128        } else {
129            return a.equals(b);
130        }
131    }
132
133    /**
134     * Comparator of {@link SimpleFragment}s according to a field.
135     */
136    public static class FieldComparator implements Comparator<SimpleFragment> {
137
138        public final String key;
139
140        public FieldComparator(String key) {
141            this.key = key;
142        }
143
144        @Override
145        public int compare(SimpleFragment frag1, SimpleFragment frag2) {
146            return doCompare(frag1, frag2);
147        }
148
149        // separate function because we need a free generic type
150        // which is incompatible with the super signature
151        @SuppressWarnings("unchecked")
152        public <T> int doCompare(SimpleFragment frag1, SimpleFragment frag2) {
153            Comparable<T> value1 = (Comparable<T>) frag1.get(key);
154            T value2 = (T) frag2.get(key);
155            if (value1 == null && value2 == null) {
156                // coherent sort
157                return frag1.hashCode() - frag2.hashCode();
158            }
159            if (value1 == null) {
160                return 1;
161            }
162            if (value2 == null) {
163                return -1;
164            }
165            return value1.compareTo(value2);
166        }
167    }
168
169}