001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id: StatementImpl.java 20796 2007-06-19 09:52:03Z sfermigier $
018 */
019
020package org.nuxeo.ecm.platform.relations.api.impl;
021
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.nuxeo.ecm.platform.relations.api.Blank;
029import org.nuxeo.ecm.platform.relations.api.Node;
030import org.nuxeo.ecm.platform.relations.api.Resource;
031import org.nuxeo.ecm.platform.relations.api.Statement;
032import org.nuxeo.ecm.platform.relations.api.Subject;
033import org.nuxeo.ecm.platform.relations.api.exceptions.InvalidPredicateException;
034import org.nuxeo.ecm.platform.relations.api.exceptions.InvalidStatementException;
035import org.nuxeo.ecm.platform.relations.api.exceptions.InvalidSubjectException;
036
037/**
038 * Statement with subject, predicate and object.
039 *
040 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
041 */
042// TODO: make a statement handle metadata (as properties)
043public class StatementImpl implements Statement {
044
045    private static final long serialVersionUID = 1L;
046
047    protected Subject subject;
048
049    protected Resource predicate;
050
051    protected Node object;
052
053    protected Map<Resource, Node[]> properties = new HashMap<Resource, Node[]>();
054
055    /**
056     * Constructor for NULL statement.
057     */
058    public StatementImpl() {
059    }
060
061    /**
062     * Constructor.
063     *
064     * @param subject Resource or Blank node
065     * @param predicate Resource
066     * @param object Resource, Blank or Literal node
067     */
068    public StatementImpl(Node subject, Node predicate, Node object) {
069        boolean validSubject = true;
070        try {
071            setSubject(subject);
072        } catch (InvalidSubjectException e) {
073            validSubject = false;
074        }
075        boolean validPredicate = true;
076        try {
077            setPredicate(predicate);
078        } catch (InvalidPredicateException e) {
079            validPredicate = false;
080        }
081        if (!validPredicate && !validSubject) {
082            throw new InvalidStatementException();
083        } else if (!validSubject) {
084            throw new InvalidSubjectException();
085        } else if (!validPredicate) {
086            throw new InvalidPredicateException();
087        }
088        this.object = object;
089    }
090
091    public Node getObject() {
092        return object;
093    }
094
095    public void setObject(Node object) {
096        this.object = object;
097    }
098
099    public Resource getPredicate() {
100        return predicate;
101    }
102
103    public void setPredicate(Node predicate) {
104        if (predicate != null && !predicate.isResource()) {
105            throw new InvalidPredicateException();
106        }
107        this.predicate = (Resource) predicate;
108    }
109
110    public Subject getSubject() {
111        return subject;
112    }
113
114    public void setSubject(Node subject) {
115        if (subject != null) {
116            if (subject instanceof Subject) {
117                this.subject = (Subject) subject;
118            } else {
119                throw new InvalidSubjectException();
120            }
121        }
122    }
123
124    public Map<Resource, Node[]> getProperties() {
125        return properties;
126    }
127
128    public Map<String, Node[]> getStringProperties() {
129        Map<String, Node[]> stringProps = new HashMap<String, Node[]>();
130        for (Map.Entry<Resource, Node[]> property : properties.entrySet()) {
131            stringProps.put(property.getKey().getUri(), property.getValue());
132        }
133        return stringProps;
134    }
135
136    public Node getProperty(Resource property) {
137        // return first one found
138        Node node = null;
139        Node[] values = properties.get(property);
140        if (values != null && values.length > 0) {
141            node = values[0];
142        }
143        return node;
144    }
145
146    public Node[] getProperties(Resource property) {
147        Node[] values = properties.get(property);
148        return values;
149    }
150
151    public void setProperties(Map<Resource, Node[]> properties) {
152        if (properties != null) {
153            for (Map.Entry<Resource, Node[]> property : properties.entrySet()) {
154                setProperties(property.getKey(), property.getValue());
155            }
156        } else {
157            this.properties.clear();
158        }
159    }
160
161    public void setProperty(Resource property, Node value) {
162        if (property != null && value != null) {
163            Node[] values = { value };
164            properties.put(property, values);
165        }
166    }
167
168    public void setProperties(Resource property, Node[] values) {
169        if (property != null && values != null && values.length > 0) {
170            properties.put(property, values);
171        }
172    }
173
174    public void deleteProperties() {
175        properties.clear();
176    }
177
178    public void deleteProperty(Resource property) {
179        properties.remove(property);
180    }
181
182    public void deleteProperty(Resource property, Node value) {
183        if (properties.containsKey(property)) {
184            List<Node> valuesList = new ArrayList<Node>();
185            valuesList.addAll(Arrays.asList(properties.get(property)));
186            valuesList.remove(value);
187            if (valuesList.isEmpty()) {
188                properties.remove(property);
189            } else {
190                properties.put(property, valuesList.toArray(new Node[] {}));
191            }
192        }
193    }
194
195    public void deleteProperties(Resource property, Node[] values) {
196        if (properties.containsKey(property) && values != null && values.length > 0) {
197            List<Node> valuesList = new ArrayList<Node>();
198            valuesList.addAll(Arrays.asList(properties.get(property)));
199            valuesList.removeAll(Arrays.asList(values));
200            if (valuesList.isEmpty()) {
201                properties.remove(property);
202            } else {
203                properties.put(property, valuesList.toArray(new Node[] {}));
204            }
205        }
206    }
207
208    public void addProperties(Map<Resource, Node[]> properties) {
209        if (properties != null) {
210            for (Map.Entry<Resource, Node[]> property : properties.entrySet()) {
211                addProperties(property.getKey(), property.getValue());
212            }
213        }
214    }
215
216    public void addProperty(Resource property, Node value) {
217        if (property != null && value != null) {
218            if (properties.containsKey(property)) {
219                List<Node> valuesList = new ArrayList<Node>();
220                valuesList.addAll(Arrays.asList(properties.get(property)));
221                if (!valuesList.contains(value)) {
222                    valuesList.add(value);
223                    properties.put(property, valuesList.toArray(new Node[] {}));
224                }
225            } else {
226                Node[] values = { value };
227                properties.put(property, values);
228            }
229        }
230    }
231
232    public void addProperties(Resource property, Node[] values) {
233        if (property != null && values != null && values.length > 0) {
234            if (properties.containsKey(property)) {
235                // add only missing nodes
236                List<Node> valuesList = new ArrayList<Node>();
237                valuesList.addAll(Arrays.asList(properties.get(property)));
238                boolean changed = false;
239                for (Node value : values) {
240                    if (!valuesList.contains(value)) {
241                        valuesList.add(value);
242                        changed = true;
243                    }
244                }
245                if (changed) {
246                    properties.put(property, valuesList.toArray(new Node[] {}));
247                }
248            } else {
249                properties.put(property, values);
250            }
251        }
252    }
253
254    @Override
255    public String toString() {
256        return String.format("%s(%s, %s, %s)", getClass().getSimpleName(), subject, predicate, object);
257    }
258
259    @Override
260    public boolean equals(Object other) {
261        if (this == other) {
262            return true;
263        }
264        if (!(other instanceof StatementImpl)) {
265            return false;
266        }
267        StatementImpl otherStatement = (StatementImpl) other;
268        return subject.equals(otherStatement.subject) && predicate.equals(otherStatement.predicate)
269                && object.equals(otherStatement.object);
270    }
271
272    @Override
273    public int hashCode() {
274        int result = 17;
275        result = 17 * result + subject.hashCode();
276        result = 17 * result + predicate.hashCode();
277        result = 17 * result + object.hashCode();
278        return result;
279    }
280
281    public int compareTo(Statement o) {
282        // dumb implementation, just used to compare statements lists
283        return toString().compareTo(o.toString());
284    }
285
286    @Override
287    public Object clone() {
288        StatementImpl clone;
289        try {
290            clone = (StatementImpl) super.clone();
291        } catch (CloneNotSupportedException e) {
292            throw new RuntimeException(e);
293        }
294        Map<Resource, Node[]> clonedProperties = new HashMap<Resource, Node[]>();
295        for (Map.Entry<Resource, Node[]> property : properties.entrySet()) {
296            clonedProperties.put(property.getKey(), property.getValue().clone());
297        }
298        clone.properties = clonedProperties;
299        return clone;
300    }
301
302}