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