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