KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > internal > intro > impl > model > AbstractIntroContainer


1 /*******************************************************************************
2  * Copyright (c) 2004, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.ui.internal.intro.impl.model;
13
14 import java.util.Iterator JavaDoc;
15 import java.util.Vector JavaDoc;
16
17 import org.eclipse.core.runtime.IConfigurationElement;
18 import org.eclipse.help.UAContentFilter;
19 import org.eclipse.help.internal.UAElementFactory;
20 import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager;
21 import org.eclipse.ui.internal.intro.impl.util.IntroEvaluationContext;
22 import org.eclipse.ui.internal.intro.impl.util.Log;
23 import org.eclipse.ui.internal.intro.impl.util.StringUtil;
24 import org.osgi.framework.Bundle;
25 import org.w3c.dom.Element JavaDoc;
26 import org.w3c.dom.Node JavaDoc;
27 import org.w3c.dom.NodeList JavaDoc;
28
29 /**
30  * An intro config component that is a container, ie: it can have children.
31  */

32 public abstract class AbstractIntroContainer extends AbstractBaseIntroElement {
33
34     protected static final String JavaDoc ATT_BG_IMAGE = "bgImage"; //$NON-NLS-1$
35
// vector is lazily created when children are loaded in a call to
36
// loadChildren().
37
protected Vector JavaDoc children;
38     protected boolean loaded = false;
39     protected boolean resolved = false;
40     protected Element element;
41
42     // store the base since it will needed later to resolve children.
43
protected String JavaDoc base;
44
45     /**
46      * @param element
47      */

48     AbstractIntroContainer(IConfigurationElement element) {
49         super(element);
50     }
51
52     /**
53      * @param element
54      */

55     AbstractIntroContainer(Element element, Bundle bundle) {
56         super(element, bundle);
57         this.element = element;
58     }
59
60     /**
61      * @param element
62      */

63     AbstractIntroContainer(Element element, Bundle bundle, String JavaDoc base) {
64         super(element, bundle);
65         this.element = element;
66         this.base = base;
67     }
68
69
70     /**
71      * Get the children of this container. Loading children and resolving
72      * includes and extension is delayed until this method call.
73      *
74      * @return Returns all the children of this container.
75      */

76     public AbstractIntroElement[] getChildren() {
77         if (!loaded)
78             loadChildren();
79
80         if (!loaded)
81             // if loaded still is false, something went wrong. This could happen
82
// when loading content from another external content files.
83
return new AbstractIntroElement[0];
84
85         if (!resolved)
86             resolveChildren();
87
88         Vector JavaDoc filtered = filterChildren(children);
89         
90         AbstractIntroElement[] childrenElements = (AbstractIntroElement[]) convertToModelArray(
91             filtered, AbstractIntroElement.ELEMENT);
92         return childrenElements;
93     }
94
95     /**
96      * Returns all the children of this container that are of the specified
97      * type(s). <br>
98      * An example of an element mask is as follows:
99      * <p>
100      * <code>
101      * int elementMask = IntroElement.IMAGE | IntroElement.DEFAULT_LINK;
102      * int elementMask = IntroElement.ABSTRACT_CONTAINER;
103      * </code>
104      * The return type is determined depending on the mask. If the mask is a
105      * predefined constant in the IntroElement, and it does not correspond to an
106      * abstract model class, then the object returned can be safely cast to an
107      * array of the corresponding model class. For exmaple, the following code
108      * gets all groups in the given page, in the same order they appear in the
109      * plugin.xml markup:
110      * <p>
111      * <code>
112      * Introgroup[] groups = (IntroGroup[])page.getChildrenOfType(IntroElement.GROUP);
113      * </code>
114      *
115      * However, if the element mask is not homogenous (for example: LINKS |
116      * GROUP) then the returned array must be cast to an array of
117      * IntroElements.For exmaple, the following code gets all images and links
118      * in the given page, in the same order they appear in the plugin.xml
119      * markup:
120      * <p>
121      * <code>
122      * int elementMask = IntroElement.IMAGE | IntroElement.DEFAULT_LINK;
123      * IntroElement[] imagesAndLinks =
124      * (IntroElement[])page.getChildrenOfType(elementMask);
125      * </code>
126      *
127      * @return An array of elements of the right type. If the container has no
128      * children, or no children of the specified types, returns an empty
129      * array.
130      */

131     public Object JavaDoc[] getChildrenOfType(int elementMask) {
132
133         AbstractIntroElement[] childrenElements = getChildren();
134         // if we have no children, we still need to return an empty array of
135
// the correct type.
136
Vector JavaDoc typedChildren = new Vector JavaDoc();
137         for (int i = 0; i < childrenElements.length; i++) {
138             AbstractIntroElement element = childrenElements[i];
139             if (element.isOfType(elementMask))
140                 typedChildren.addElement(element);
141         }
142         return convertToModelArray(typedChildren, elementMask);
143     }
144
145     /**
146      * Utility method to convert all the content of a vector of
147      * AbstractIntroElements into an array of IntroElements cast to the correct
148      * class type. It is assumed that all elements in this vector are
149      * IntroElement instances. If elementMask is a predefined model type (ie:
150      * homogenous), then return array of corresponding type. Else, returns an
151      * array of IntroElements.
152      *
153      * @param vector
154      */

155     private Object JavaDoc[] convertToModelArray(Vector JavaDoc vector, int elementMask) {
156         int size = vector.size();
157         Object JavaDoc[] src = null;
158         switch (elementMask) {
159         // homogenous vector.
160
case AbstractIntroElement.GROUP:
161             src = new IntroGroup[size];
162             break;
163         case AbstractIntroElement.LINK:
164             src = new IntroLink[size];
165             break;
166         case AbstractIntroElement.TEXT:
167             src = new IntroText[size];
168             break;
169         case AbstractIntroElement.IMAGE:
170             src = new IntroImage[size];
171             break;
172         case AbstractIntroElement.HR:
173             src = new IntroSeparator[size];
174             break;
175         case AbstractIntroElement.HTML:
176             src = new IntroHTML[size];
177             break;
178         case AbstractIntroElement.INCLUDE:
179             src = new IntroInclude[size];
180             break;
181         case AbstractIntroElement.PAGE:
182             src = new IntroPage[size];
183             break;
184         case AbstractIntroElement.ABSTRACT_PAGE:
185             src = new AbstractIntroPage[size];
186             break;
187         case AbstractIntroElement.ABSTRACT_CONTAINER:
188             src = new AbstractIntroContainer[size];
189             break;
190         case AbstractIntroElement.HEAD:
191             src = new IntroHead[size];
192             break;
193         case AbstractIntroElement.PAGE_TITLE:
194             src = new IntroPageTitle[size];
195             break;
196         case AbstractIntroElement.ANCHOR:
197             src = new IntroAnchor[size];
198             break;
199         case AbstractIntroElement.CONTENT_PROVIDER:
200             src = new IntroContentProvider[size];
201             break;
202
203         default:
204             // now handle left over abstract types. Vector is not homogenous.
205
src = new AbstractIntroElement[size];
206             break;
207         }
208         if (src == null)
209             return new Object JavaDoc[0];
210
211         vector.copyInto(src);
212         return src;
213
214     }
215
216     /**
217      * Load all the children of this container. A container can have other
218      * containers, links, htmls, text, image, include. Load them in the order
219      * they appear in the xml content file.
220      */

221     protected void loadChildren() {
222         // init the children vector. old children are disposed automatically.
223
children = new Vector JavaDoc();
224         
225
226         NodeList JavaDoc nodeList = element.getChildNodes();
227         Vector JavaDoc vector = new Vector JavaDoc();
228         for (int i = 0; i < nodeList.getLength(); i++) {
229             Node JavaDoc node = nodeList.item(i);
230             if (node.getNodeType() == Node.ELEMENT_NODE)
231                 vector.add(node);
232         }
233         Element[] filteredElements = new Element[vector.size()];
234         vector.copyInto(filteredElements);
235         // add the elements at the end children's vector.
236
insertElementsBefore(filteredElements, getBundle(), base, children
237             .size(), null);
238         loaded = true;
239         // we cannot free DOM model element because a page's children may be
240
// nulled when reflowing a content provider.
241
}
242
243     /**
244      * Adds the given elements as children of this container, before the
245      * specified index.
246      *
247      * @param childElements
248      */

249     protected void insertElementsBefore(Element[] childElements, Bundle bundle,
250             String JavaDoc base, int index, String JavaDoc mixinStyle) {
251         for (int i = 0; i < childElements.length; i++) {
252             Element childElement = childElements[i];
253             AbstractIntroElement child = getModelChild(childElement, bundle,
254                 base);
255             if (child != null) {
256                 child.setParent(this);
257                 child.setMixinStyle(mixinStyle);
258                 children.add(index, child);
259                 // index is only incremented if we actually added a child.
260
index++;
261             }
262         }
263     }
264
265     /**
266      * Adds the given elements as children of this container, before the
267      * specified element. The element must be a direct child of this container.
268      *
269      * @param childElements
270      */

271     protected void insertElementsBefore(Element[] childElements, Bundle bundle,
272             String JavaDoc base, AbstractIntroElement child, String JavaDoc mixinStyle) {
273         int childLocation = children.indexOf(child);
274         if (childLocation == -1)
275             // bad reference child.
276
return;
277         insertElementsBefore(childElements, bundle, base, childLocation, mixinStyle);
278     }
279
280
281
282     /**
283      * Adds a child to this container, depending on its type. Subclasses may
284      * override if there is a child specific to the subclass.
285      *
286      * @param childElements
287      */

288     protected AbstractIntroElement getModelChild(Element childElement,
289             Bundle bundle, String JavaDoc base) {
290
291         AbstractIntroElement child = null;
292         if (childElement.getNodeName().equalsIgnoreCase(IntroGroup.TAG_GROUP))
293             child = new IntroGroup(childElement, bundle, base);
294         else if (childElement.getNodeName()
295             .equalsIgnoreCase(IntroLink.TAG_LINK))
296             child = new IntroLink(childElement, bundle, base);
297         else if (childElement.getNodeName()
298             .equalsIgnoreCase(IntroText.TAG_TEXT))
299             child = new IntroText(childElement, bundle);
300         else if (childElement.getNodeName().equalsIgnoreCase(
301             IntroImage.TAG_IMAGE))
302             child = new IntroImage(childElement, bundle, base);
303         else if (childElement.getNodeName().equalsIgnoreCase(
304                 IntroSeparator.TAG_HR))
305             child = new IntroSeparator(childElement, bundle, base);
306         else if (childElement.getNodeName()
307             .equalsIgnoreCase(IntroHTML.TAG_HTML))
308             child = new IntroHTML(childElement, bundle, base);
309         else if (childElement.getNodeName().equalsIgnoreCase(
310             IntroInclude.TAG_INCLUDE))
311             child = new IntroInclude(childElement, bundle);
312         else if (childElement.getNodeName().equalsIgnoreCase(
313             IntroAnchor.TAG_ANCHOR))
314             child = new IntroAnchor(childElement, bundle);
315         else if (childElement.getNodeName().equalsIgnoreCase(
316             IntroContentProvider.TAG_CONTENT_PROVIDER))
317             child = new IntroContentProvider(childElement, bundle);
318         return child;
319     }
320
321
322     /**
323      * Resolve each include in this container's children. Includes are lazily
324      * resolved on a per container basis, when the container is resolved.
325      */

326     protected void resolveChildren() {
327         AbstractIntroElement[] array = (AbstractIntroElement[])children.toArray(new AbstractIntroElement[children.size()]);
328         for (int i=0;i<array.length;++i) {
329             AbstractIntroElement child = array[i];
330             if (UAContentFilter.isFiltered(UAElementFactory.newElement(child.getElement()), IntroEvaluationContext.getContext())) {
331                 children.remove(child);
332             }
333             else if (child.getType() == AbstractIntroElement.INCLUDE) {
334                 resolveInclude((IntroInclude) child);
335             }
336         }
337         resolved = true;
338     }
339
340     /**
341      * Resolves an include. Gets the intro element pointed to by the include,
342      * and adds it as a child of this current container. If target is not a
343      * group, or any element that can be included in a group, ignore this
344      * include.
345      *
346      * @param include
347      */

348     private void resolveInclude(IntroInclude include) {
349         AbstractIntroElement target = findIncludeTarget(include);
350         if (target == null)
351             // target could not be found.
352
return;
353         if (target.isOfType(AbstractIntroElement.GROUP
354                 | AbstractIntroElement.ABSTRACT_TEXT
355                 | AbstractIntroElement.IMAGE | AbstractIntroElement.TEXT
356                 | AbstractIntroElement.PAGE_TITLE))
357             // be picky about model elements to include. Can not use
358
// BASE_ELEMENT model class because pages can not be included.
359
insertTarget(include, target);
360     }
361
362     /**
363      * Filters the appropriate elements from the given Vector, according to the current
364      * environment. For example, if one of the elements has a tag to filter for os=linux and
365      * the os is win32, the element will not be returned in the resulting Vector.
366      *
367      * @param unfiltered the unfiltered elements
368      * @return a new Vector with elements filtered
369      */

370     private Vector JavaDoc filterChildren(Vector JavaDoc unfiltered) {
371         Vector JavaDoc filtered = new Vector JavaDoc();
372         Iterator JavaDoc iter = unfiltered.iterator();
373         while (iter.hasNext()) {
374             Object JavaDoc element = iter.next();
375             if (!UAContentFilter.isFiltered(element, IntroEvaluationContext.getContext())) {
376                 filtered.add(element);
377             }
378         }
379         return filtered;
380     }
381     
382     /**
383      * Find the target element pointed to by the path in the include. It is
384      * assumed that configId always points to an external config, and not the
385      * same config of the inlcude.
386      *
387      * @param include
388      * @param path
389      * @return
390      */

391     private AbstractIntroElement findIncludeTarget(IntroInclude include) {
392         String JavaDoc path = include.getPath();
393         IntroModelRoot targetModelRoot = (IntroModelRoot) getParentPage()
394             .getParent();
395         String JavaDoc targetConfigID = include.getConfigId();
396         if (targetConfigID != null)
397             targetModelRoot = ExtensionPointManager.getInst().getModel(
398                 targetConfigID);
399         if (targetModelRoot == null)
400             // if the target config was not found, skip this include.
401
return null;
402         AbstractIntroElement target = findTarget(targetModelRoot, path);
403         return target;
404     }
405
406     /**
407      * Finds the child element that corresponds to the given path in the passed
408      * model.<br>
409      * ps: This method could be a static method, but left as instance for model
410      * enhancements.
411      *
412      * @param model
413      * @param path
414      * @return
415      */

416     public AbstractIntroElement findTarget(AbstractIntroContainer container,
417             String JavaDoc path) {
418         // extract path segments. Get first segment to start search.
419
String JavaDoc[] pathSegments = StringUtil.split(path, "/"); //$NON-NLS-1$
420
if (container == null)
421             return null;
422         
423         AbstractIntroElement target = container.findChild(pathSegments[0]);
424         if (target == null)
425             // there is no direct child with the specified first path segment.
426
return null;
427
428         // found parent segment. now find each child segment.
429
for (int i = 1; i < pathSegments.length; i++) {
430             if (!(target instanceof AbstractIntroContainer)) {
431                 // parent is not a container, so no point going on.
432
return null;
433             }
434             String JavaDoc pathSegment = pathSegments[i];
435             target = ((AbstractIntroContainer) target).findChild(pathSegment);
436             if (target == null)
437                 // tried to find next segment and failed.
438
return null;
439         }
440         return target;
441     }
442     
443     public AbstractIntroElement findTarget(AbstractIntroContainer container,
444             String JavaDoc path, String JavaDoc extensionId) {
445         // resolve path segments if they are incomplete.
446
if (path.indexOf("@")!= -1) { //$NON-NLS-1$
447
// new in 3.2: dynamic resolution of incomplete target paths
448
IntroModelRoot root = getModelRoot();
449             if (root!=null) {
450                 path = root.resolvePath(extensionId, path);
451                 if (path==null)
452                     return null;
453             }
454
455         }
456         return this.findTarget(container, path);
457     }
458
459
460     public AbstractIntroElement findTarget(String JavaDoc path) {
461         return findTarget(this, path);
462     }
463
464
465
466     /*
467      * searches direct children for the first child with the given id. The type
468      * of the child can be any model element that has an id. ie:
469      * AbstractIntroIdElement
470      *
471      * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
472      */

473     public AbstractIntroElement findChild(String JavaDoc elementId) {
474         return findChild(elementId, ID_ELEMENT);
475     }
476
477     /*
478      * searches direct children for the first child with the given id. The type
479      * of the child must be of the passed model types mask. This method handles
480      * the 3.0 style model for content. Pages enhance this behavior with DOM
481      * apis.
482      *
483      * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
484      */

485     public AbstractIntroElement findChild(String JavaDoc elementId, int elementMask) {
486         if (!loaded)
487             loadChildren();
488
489         for (int i = 0; i < children.size(); i++) {
490             AbstractIntroElement aChild = (AbstractIntroElement) children
491                 .elementAt(i);
492             if (!aChild.isOfType(ID_ELEMENT))
493                 // includes and heads do not have ids, and so can not be
494
// referenced directly. This means that they can not be
495
// targets for other includes. Skip, just in case someone
496
// adds an id to it! Also, this applies to all elements in
497
// the model that do not have ids.
498
continue;
499             AbstractIntroIdElement child = (AbstractIntroIdElement) aChild;
500             if (child.getId() != null && child.getId().equals(elementId)
501                     && child.isOfType(elementMask))
502                 return child;
503         }
504         // no child with given id and type found.
505
return null;
506     }
507
508
509
510     private void insertTarget(IntroInclude include, AbstractIntroElement target) {
511         int includeLocation = children.indexOf(include);
512         if (includeLocation == -1)
513             // should never be here.
514
return;
515         children.remove(includeLocation);
516         // handle merging target styles first, before changing target parent to
517
// enable inheritance of styles.
518
handleIncludeStyleInheritence(include, target);
519         // now clone the target node because original model should be kept
520
// intact.
521
AbstractIntroElement clonedTarget = null;
522         try {
523             clonedTarget = (AbstractIntroElement) target.clone();
524         } catch (CloneNotSupportedException JavaDoc ex) {
525             // should never be here.
526
Log.error("Failed to clone Intro model node.", ex); //$NON-NLS-1$
527
return;
528         }
529         // set parent of cloned target to be this container.
530
clonedTarget.setParent(this);
531         children.insertElementAt(clonedTarget, includeLocation);
532     }
533
534     /**
535      * Updates the inherited styles based on the merge-style attribute. If we
536      * are including a shared group, or if we are including an element from the
537      * same page, do nothing. For inherited alt-styles, we have to cache the pd
538      * from which we inherited the styles to be able to access resources in that
539      * plugin. Also note that when including a container, it must be resolved
540      * otherwise reparenting will cause includes in this target container to
541      * fail.
542      *
543      * @param include
544      * @param target
545      */

546     private void handleIncludeStyleInheritence(IntroInclude include,
547             AbstractIntroElement target) {
548
549         if (include.getMergeStyle() == false)
550             // target styles are not needed. nothing to do.
551
return;
552
553         if (target.getParent().getType() == AbstractIntroElement.MODEL_ROOT
554                 || target.getParentPage().equals(include.getParentPage()))
555             // If we are including from this same page ie: target is in the
556
// same page, OR if we are including a shared group, defined
557
// under a config, do not include styles.
558
return;
559
560         // Update the parent page styles. skip style if it is null. Note,
561
// include both the target page styles and inherited styles. The full
562
// page styles need to be include.
563
String JavaDoc style = target.getParentPage().getStyle();
564         if (style != null)
565             getParentPage().addStyle(style);
566
567         // for alt-style cache bundle for loading resources.
568
style = target.getParentPage().getAltStyle();
569         if (style != null) {
570             Bundle bundle = target.getBundle();
571             getParentPage().addAltStyle(style, bundle);
572         }
573
574         // now add inherited styles. Race condition could happen here if Page A
575
// is including from Page B which is in turn including from Page A.
576
getParentPage().addStyles(target.getParentPage().getStyles());
577         getParentPage().addAltStyles(target.getParentPage().getAltStyles());
578
579     }
580
581     /**
582      * Creates a clone of the given target node. A clone is create by simply
583      * recreating that protion of the model.
584      *
585      * Note: looked into the clonable interface in Java, but it was not used
586      * because it makes modifications/additions to the model harder to maintain.
587      * Will revisit later.
588      *
589      * @param targer
590      * @return
591      */

592     protected AbstractIntroElement cloneTarget(AbstractIntroElement target) {
593         return null;
594     }
595
596
597     /*
598      * (non-Javadoc)
599      *
600      * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
601      */

602     public int getType() {
603         return AbstractIntroElement.ABSTRACT_CONTAINER;
604     }
605
606
607
608     /**
609      * Deep copy since class has mutable objects. Leave DOM element as a shallow
610      * reference copy since DOM is immutable.
611      */

612     public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
613         AbstractIntroContainer clone = (AbstractIntroContainer) super.clone();
614         clone.children = new Vector JavaDoc();
615         if (children != null) {
616             for (int i = 0; i < children.size(); i++) {
617                 AbstractIntroElement cloneChild = (AbstractIntroElement) ((AbstractIntroElement) children
618                     .elementAt(i)).clone();
619                 cloneChild.setParent(clone);
620                 clone.children.add(i, cloneChild);
621             }
622         }
623         return clone;
624     }
625
626     /**
627      * Returns the element.
628      *
629      * @return
630      */

631     public Element getElement() {
632         return this.element;
633     }
634
635     public String JavaDoc getBase() {
636         return base;
637     }
638
639
640     /*
641      * Clears this container. This means emptying the children, and resetting
642      * flags.
643      */

644     public void clearChildren() {
645         this.children.clear();
646     }
647
648
649     /**
650      * Adds a model element as a child. Caller is responsible for inserting
651      * model elements that rea valid as children.
652      *
653      * @param child
654      */

655     public void addChild(AbstractIntroElement child) {
656         children.add(child);
657     }
658     
659     public void removeChild(AbstractIntroElement child) {
660         children.remove(child);
661     }
662
663     public String JavaDoc getBackgroundImage() {
664         return getAttribute(element, ATT_BG_IMAGE);
665     }
666
667
668 }
Popular Tags