001/*
002 * (C) Copyright 2006-2008 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 *     bstefanescu
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.webengine.ui.tree;
023
024import org.nuxeo.common.utils.Path;
025
026/**
027 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
028 */
029public class TreeItemImpl implements TreeItem {
030
031    private static final long serialVersionUID = 5252830785508229998L;
032
033    public static final int F_CONTAINER = 4;
034
035    public static final int F_EXPANDED = 8;
036
037    public static final TreeItem[] EMPTY_CHILDREN = new TreeItem[0];
038
039    public static final TreeItem[] HAS_CHILDREN = new TreeItem[0];
040
041    protected final ContentProvider provider;
042
043    protected final TreeItem parent;
044
045    protected final Path path;
046
047    protected String label;
048
049    protected String[] facets;
050
051    protected TreeItem[] children = EMPTY_CHILDREN;
052
053    protected final Object obj;
054
055    protected volatile int state = BOTH;
056
057    // TODO: use a map?
058    // protected Map<String, TreeItem> childrenMap;
059
060    public TreeItemImpl(TreeItem parent, ContentProvider provider, Object data) {
061        this.parent = parent;
062        this.provider = provider;
063        obj = data;
064        String name = provider.getName(obj);
065        if (parent != null) {
066            path = parent.getPath().append(name);
067        } else {
068            path = new Path("/");
069        }
070        if (provider.isContainer(obj)) {
071            state |= F_CONTAINER; // set container flag and invalidate children
072        }
073    }
074
075    public TreeItemImpl(ContentProvider provider, Object data) {
076        this(null, provider, data);
077    }
078
079    public TreeItemImpl(TreeItem parent, Object data) {
080        this(parent, parent.getContentProvider(), data);
081    }
082
083    @Override
084    public boolean hasChildren() {
085        return children.length > 0;
086    }
087
088    @Override
089    public TreeItem[] getChildren() {
090        validateChildren();
091        return children;
092    }
093
094    @Override
095    public Object getObject() {
096        return obj;
097    }
098
099    @Override
100    public Path getPath() {
101        return path;
102    }
103
104    @Override
105    public TreeItem getParent() {
106        return parent;
107    }
108
109    @Override
110    public ContentProvider getContentProvider() {
111        return provider;
112    }
113
114    @Override
115    public String getName() {
116        return path.lastSegment();
117    }
118
119    @Override
120    public String getLabel() {
121        validateData();
122        return label;
123    }
124
125    public String[] getFacets() {
126        validateData();
127        return facets;
128    }
129
130    @Override
131    public boolean isContainer() {
132        return (state & F_CONTAINER) != 0;
133    }
134
135    @Override
136    public TreeItem find(Path path) {
137        TreeItem item = this;
138        for (int i = 0, len = path.segmentCount() - 1; i < len; i++) {
139            if (!item.hasChildren()) {
140                return null;
141            }
142            item = item.getChild(path.segment(i));
143            if (item == null) {
144                return null;
145            }
146        }
147        if (!item.hasChildren()) {
148            return null;
149        }
150        return item.getChild(path.lastSegment());
151    }
152
153    @Override
154    public TreeItem findAndReveal(Path path) {
155        // we expand only parents and not the last segment
156        TreeItem item = this;
157        int len = path.segmentCount();
158        for (int i = 0; i < len; i++) {
159            item.expand();
160            item = item.getChild(path.segment(i));
161            if (item == null) {
162                return null;
163            }
164        }
165        return item;
166    }
167
168    @Override
169    public TreeItem getChild(String name) {
170        validateChildren();
171        return _getChild(name);
172    }
173
174    protected TreeItem _getChild(String name) {
175        for (TreeItem child : children) {
176            if (name.equals(child.getName())) {
177                return child;
178            }
179        }
180        return null;
181    }
182
183    @Override
184    public TreeItem[] expand() {
185        if (isExpanded()) {
186            return children;
187        } else {
188            if (parent != null && !parent.isExpanded()) {
189                parent.expand();
190            }
191            state |= F_EXPANDED;
192            return getChildren();
193        }
194    }
195
196    protected void loadData() {
197        label = provider.getLabel(obj);
198        facets = provider.getFacets(obj);
199    }
200
201    public void validateData() {
202        if ((state & DATA) != 0) {
203            loadData();
204            state &= ~DATA;
205        }
206    }
207
208    public void validateChildren() {
209        if ((state & CHILDREN) != 0) {
210            loadChildren();
211            state &= ~CHILDREN;
212        }
213    }
214
215    protected void loadChildren() {
216        if (!isContainer()) {
217            return;
218        }
219        Object[] objects = parent == null ? provider.getElements(obj) : provider.getChildren(obj);
220        if (objects == null) {
221            children = null;
222        } else {
223            children = new TreeItemImpl[objects.length];
224            for (int i = 0; i < objects.length; i++) {
225                children[i] = new TreeItemImpl(this, objects[i]);
226            }
227        }
228    }
229
230    @Override
231    public void collapse() {
232        state &= ~F_EXPANDED;
233    }
234
235    @Override
236    public boolean isExpanded() {
237        return (state & F_EXPANDED) != 0;
238    }
239
240    /*
241     * TODO not completely implemented
242     */
243    @Override
244    public void refresh(int type) {
245        if ((type & DATA) != 0) {
246            loadData();
247        }
248        if ((type & CHILDREN) != 0) {
249            loadChildren();
250        }
251        state &= ~type;
252    }
253
254    @Override
255    public void validate() {
256        refresh(state);
257    }
258
259    @Override
260    public void invalidate(int type) {
261        state |= type;
262    }
263
264    /*
265     * TODO not implemented
266     */
267    @Override
268    public int getValidationState() {
269        return state;
270    }
271
272    @Override
273    public Object accept(TreeItemVisitor visitor) {
274        return visitor.visit(this);
275    }
276
277    @Override
278    public String toString() {
279        return "TreeItem: " + obj.toString();
280    }
281
282    @Override
283    public boolean equals(Object obj) {
284        if (obj == this) {
285            return true;
286        }
287        if (obj instanceof TreeItem) {
288            return getObject().equals(((TreeItem) obj).getObject());
289        }
290        return false;
291    }
292
293}