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    public boolean hasChildren() {
084        return children.length > 0;
085    }
086
087    public TreeItem[] getChildren() {
088        validateChildren();
089        return children;
090    }
091
092    public Object getObject() {
093        return obj;
094    }
095
096    public Path getPath() {
097        return path;
098    }
099
100    public TreeItem getParent() {
101        return parent;
102    }
103
104    public ContentProvider getContentProvider() {
105        return provider;
106    }
107
108    public String getName() {
109        return path.lastSegment();
110    }
111
112    public String getLabel() {
113        validateData();
114        return label;
115    }
116
117    public String[] getFacets() {
118        validateData();
119        return facets;
120    }
121
122    public boolean isContainer() {
123        return (state & F_CONTAINER) != 0;
124    }
125
126    public TreeItem find(Path path) {
127        TreeItem item = this;
128        for (int i = 0, len = path.segmentCount() - 1; i < len; i++) {
129            if (!item.hasChildren()) {
130                return null;
131            }
132            item = item.getChild(path.segment(i));
133            if (item == null) {
134                return null;
135            }
136        }
137        if (!item.hasChildren()) {
138            return null;
139        }
140        return item.getChild(path.lastSegment());
141    }
142
143    public TreeItem findAndReveal(Path path) {
144        // we expand only parents and not the last segment
145        TreeItem item = this;
146        int len = path.segmentCount();
147        for (int i = 0; i < len; i++) {
148            item.expand();
149            item = item.getChild(path.segment(i));
150            if (item == null) {
151                return null;
152            }
153        }
154        return item;
155    }
156
157    public TreeItem getChild(String name) {
158        validateChildren();
159        return _getChild(name);
160    }
161
162    protected TreeItem _getChild(String name) {
163        for (TreeItem child : children) {
164            if (name.equals(child.getName())) {
165                return child;
166            }
167        }
168        return null;
169    }
170
171    public TreeItem[] expand() {
172        if (isExpanded()) {
173            return children;
174        } else {
175            if (parent != null && !parent.isExpanded()) {
176                parent.expand();
177            }
178            state |= F_EXPANDED;
179            return getChildren();
180        }
181    }
182
183    protected void loadData() {
184        label = provider.getLabel(obj);
185        facets = provider.getFacets(obj);
186    }
187
188    public void validateData() {
189        if ((state & DATA) != 0) {
190            loadData();
191            state &= ~DATA;
192        }
193    }
194
195    public void validateChildren() {
196        if ((state & CHILDREN) != 0) {
197            loadChildren();
198            state &= ~CHILDREN;
199        }
200    }
201
202    protected void loadChildren() {
203        if (!isContainer()) {
204            return;
205        }
206        Object[] objects = parent == null ? provider.getElements(obj) : provider.getChildren(obj);
207        if (objects == null) {
208            children = null;
209        } else {
210            children = new TreeItemImpl[objects.length];
211            for (int i = 0; i < objects.length; i++) {
212                children[i] = new TreeItemImpl(this, objects[i]);
213            }
214        }
215    }
216
217    public void collapse() {
218        state &= ~F_EXPANDED;
219    }
220
221    public boolean isExpanded() {
222        return (state & F_EXPANDED) != 0;
223    }
224
225    /*
226     * TODO not completely implemented
227     */
228    public void refresh(int type) {
229        if ((type & DATA) != 0) {
230            loadData();
231        }
232        if ((type & CHILDREN) != 0) {
233            loadChildren();
234        }
235        state &= ~type;
236    }
237
238    public void validate() {
239        refresh(state);
240    }
241
242    public void invalidate(int type) {
243        state |= type;
244    }
245
246    /*
247     * TODO not implemented
248     */
249    public int getValidationState() {
250        return state;
251    }
252
253    public Object accept(TreeItemVisitor visitor) {
254        return visitor.visit(this);
255    }
256
257    @Override
258    public String toString() {
259        return "TreeItem: " + obj.toString();
260    }
261
262    @Override
263    public boolean equals(Object obj) {
264        if (obj == this) {
265            return true;
266        }
267        if (obj instanceof TreeItem) {
268            return getObject().equals(((TreeItem) obj).getObject());
269        }
270        return false;
271    }
272
273}