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