Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 0b1765dd6d8201915ded87f8811a1fb729c97932 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.intro.impl.model;

import java.util.Iterator;
import java.util.Vector;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.help.UAContentFilter;
import org.eclipse.help.internal.UAElementFactory;
import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager;
import org.eclipse.ui.internal.intro.impl.util.IntroEvaluationContext;
import org.eclipse.ui.internal.intro.impl.util.Log;
import org.eclipse.ui.internal.intro.impl.util.StringUtil;
import org.osgi.framework.Bundle;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * An intro config component that is a container, ie: it can have children.
 */
public abstract class AbstractIntroContainer extends AbstractBaseIntroElement {

    protected static final String ATT_BG_IMAGE = "bgImage"; //$NON-NLS-1$
	// vector is lazily created when children are loaded in a call to
    // loadChildren().
	protected Vector<AbstractIntroElement> children;
    protected boolean loaded = false;
    protected boolean resolved = false;
    protected Element element;

    // store the base since it will needed later to resolve children.
    protected String base;

    /**
     * @param element
     */
    AbstractIntroContainer(IConfigurationElement element) {
        super(element);
    }

    /**
     * @param element
     */
    AbstractIntroContainer(Element element, Bundle bundle) {
        super(element, bundle);
        this.element = element;
    }

    /**
     * @param element
     */
    AbstractIntroContainer(Element element, Bundle bundle, String base) {
        super(element, bundle);
        this.element = element;
        this.base = base;
    }


    /**
     * Get the children of this container. Loading children and resolving
     * includes and extension is delayed until this method call.
     * 
     * @return Returns all the children of this container.
     */
    public AbstractIntroElement[] getChildren() {
        if (!loaded)
            loadChildren();

        if (!loaded)
            // if loaded still is false, something went wrong. This could happen
            // when loading content from another external content files.
            return new AbstractIntroElement[0];

        if (!resolved)
            resolveChildren();

        Vector filtered = filterChildren(children);
        
        AbstractIntroElement[] childrenElements = (AbstractIntroElement[]) convertToModelArray(
        	filtered, AbstractIntroElement.ELEMENT);
        return childrenElements;
    }

    /**
     * Returns all the children of this container that are of the specified
     * type(s). <br>
     * An example of an element mask is as follows:
     * <p>
     * <code>
     * 		int elementMask = IntroElement.IMAGE | IntroElement.DEFAULT_LINK;
     * 		int elementMask = IntroElement.ABSTRACT_CONTAINER; 
     * </code>
     * The return type is determined depending on the mask. If the mask is a
     * predefined constant in the IntroElement, and it does not correspond to an
     * abstract model class, then the object returned can be safely cast to an
     * array of the corresponding model class. For exmaple, the following code
     * gets all groups in the given page, in the same order they appear in the
     * plugin.xml markup:
     * <p>
     * <code>
     * 		Introgroup[] groups  = (IntroGroup[])page.getChildrenOfType(IntroElement.GROUP);
     * </code>
     * 
     * However, if the element mask is not homogenous (for example: LINKS |
     * GROUP) then the returned array must be cast to an array of
     * IntroElements.For exmaple, the following code gets all images and links
     * in the given page, in the same order they appear in the plugin.xml
     * markup:
     * <p>
     * <code>
     * 		int elementMask = IntroElement.IMAGE | IntroElement.DEFAULT_LINK;
     * 		IntroElement[] imagesAndLinks  = 
     * 			(IntroElement[])page.getChildrenOfType(elementMask);
     * </code>
     * 
     * @return An array of elements of the right type. If the container has no
     *         children, or no children of the specified types, returns an empty
     *         array.
     */
    public Object[] getChildrenOfType(int elementMask) {

        AbstractIntroElement[] childrenElements = getChildren();
        // if we have no children, we still need to return an empty array of
        // the correct type.
		Vector<AbstractIntroElement> typedChildren = new Vector<>();
        for (int i = 0; i < childrenElements.length; i++) {
            AbstractIntroElement element = childrenElements[i];
            if (element.isOfType(elementMask))
                typedChildren.addElement(element);
        }
        return convertToModelArray(typedChildren, elementMask);
    }

    /**
     * Utility method to convert all the content of a vector of
     * AbstractIntroElements into an array of IntroElements cast to the correct
     * class type. It is assumed that all elements in this vector are
     * IntroElement instances. If elementMask is a predefined model type (ie:
     * homogenous), then return array of corresponding type. Else, returns an
     * array of IntroElements.
     * 
     * @param vector
     */
    private Object[] convertToModelArray(Vector vector, int elementMask) {
        int size = vector.size();
        Object[] src = null;
        switch (elementMask) {
        // homogenous vector.
        case AbstractIntroElement.GROUP:
            src = new IntroGroup[size];
            break;
        case AbstractIntroElement.LINK:
            src = new IntroLink[size];
            break;
        case AbstractIntroElement.TEXT:
            src = new IntroText[size];
            break;
        case AbstractIntroElement.IMAGE:
            src = new IntroImage[size];
            break;
        case AbstractIntroElement.HR:
            src = new IntroSeparator[size];
            break;
        case AbstractIntroElement.HTML:
            src = new IntroHTML[size];
            break;
        case AbstractIntroElement.INCLUDE:
            src = new IntroInclude[size];
            break;
        case AbstractIntroElement.PAGE:
            src = new IntroPage[size];
            break;
        case AbstractIntroElement.ABSTRACT_PAGE:
            src = new AbstractIntroPage[size];
            break;
        case AbstractIntroElement.ABSTRACT_CONTAINER:
            src = new AbstractIntroContainer[size];
            break;
        case AbstractIntroElement.HEAD:
            src = new IntroHead[size];
            break;
        case AbstractIntroElement.PAGE_TITLE:
            src = new IntroPageTitle[size];
            break;
        case AbstractIntroElement.ANCHOR:
            src = new IntroAnchor[size];
            break;
        case AbstractIntroElement.CONTENT_PROVIDER:
            src = new IntroContentProvider[size];
            break;

        default:
            // now handle left over abstract types. Vector is not homogenous.
            src = new AbstractIntroElement[size];
            break;
        }
        
        vector.copyInto(src);
        return src;

    }

    /**
     * Load all the children of this container. A container can have other
     * containers, links, htmls, text, image, include. Load them in the order
     * they appear in the xml content file.
     */
    protected void loadChildren() {
        // init the children vector. old children are disposed automatically.
		children = new Vector<>();
        

        NodeList nodeList = element.getChildNodes();
		Vector<Node> vector = new Vector<>();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE)
                vector.add(node);
        }
        Element[] filteredElements = new Element[vector.size()];
        vector.copyInto(filteredElements);
        // add the elements at the end children's vector.
        insertElementsBefore(filteredElements, getBundle(), base, children
            .size(), null);
        loaded = true;
        // we cannot free DOM model element because a page's children may be
        // nulled when reflowing a content provider.
    }

    /**
     * Adds the given elements as children of this container, before the
     * specified index.
     * 
     * @param childElements
     */
    protected void insertElementsBefore(Element[] childElements, Bundle bundle,
            String base, int index, String mixinStyle) {
        for (int i = 0; i < childElements.length; i++) {
            Element childElement = childElements[i];
            AbstractIntroElement child = getModelChild(childElement, bundle,
                base);
            if (child != null) {
                child.setParent(this);
                child.setMixinStyle(mixinStyle);
                children.add(index, child);
                // index is only incremented if we actually added a child.
                index++;
            }
        }
    }

    /**
     * Adds the given elements as children of this container, before the
     * specified element. The element must be a direct child of this container.
     * 
     * @param childElements
     */
    protected void insertElementsBefore(Element[] childElements, Bundle bundle,
            String base, AbstractIntroElement child, String mixinStyle) {
        int childLocation = children.indexOf(child);
        if (childLocation == -1)
            // bad reference child.
            return;
        insertElementsBefore(childElements, bundle, base, childLocation, mixinStyle);
    }



    /**
     * Adds a child to this container, depending on its type. Subclasses may
     * override if there is a child specific to the subclass.
     * 
     * @param childElements
     */
    protected AbstractIntroElement getModelChild(Element childElement,
            Bundle bundle, String base) {

        AbstractIntroElement child = null;
        if (childElement.getNodeName().equalsIgnoreCase(IntroGroup.TAG_GROUP))
            child = new IntroGroup(childElement, bundle, base);
        else if (childElement.getNodeName()
            .equalsIgnoreCase(IntroLink.TAG_LINK))
            child = new IntroLink(childElement, bundle, base);
        else if (childElement.getNodeName()
            .equalsIgnoreCase(IntroText.TAG_TEXT))
            child = new IntroText(childElement, bundle);
        else if (childElement.getNodeName().equalsIgnoreCase(
            IntroImage.TAG_IMAGE))
            child = new IntroImage(childElement, bundle, base);
        else if (childElement.getNodeName().equalsIgnoreCase(
                IntroSeparator.TAG_HR))
            child = new IntroSeparator(childElement, bundle, base);
        else if (childElement.getNodeName()
            .equalsIgnoreCase(IntroHTML.TAG_HTML))
            child = new IntroHTML(childElement, bundle, base);
        else if (childElement.getNodeName().equalsIgnoreCase(
            IntroInclude.TAG_INCLUDE))
            child = new IntroInclude(childElement, bundle);
        else if (childElement.getNodeName().equalsIgnoreCase(
            IntroAnchor.TAG_ANCHOR))
            child = new IntroAnchor(childElement, bundle);
        else if (childElement.getNodeName().equalsIgnoreCase(
            IntroContentProvider.TAG_CONTENT_PROVIDER))
            child = new IntroContentProvider(childElement, bundle);
        return child;
    }


    /**
     * Resolve each include in this container's children. Includes are lazily
     * resolved on a per container basis, when the container is resolved.
     */
    protected void resolveChildren() {
    	AbstractIntroElement[] array = (AbstractIntroElement[])children.toArray(new AbstractIntroElement[children.size()]);
    	for (int i=0;i<array.length;++i) {
            AbstractIntroElement child = array[i];
            if (UAContentFilter.isFiltered(UAElementFactory.newElement(child.getElement()), IntroEvaluationContext.getContext())) {
            	children.remove(child);
            }
            else if (child.getType() == AbstractIntroElement.INCLUDE) {
                resolveInclude((IntroInclude) child);
            }
    	}
        resolved = true;
    }

    /**
     * Resolves an include. Gets the intro element pointed to by the include,
     * and adds it as a child of this current container. If target is not a
     * group, or any element that can be included in a group, ignore this
     * include.
     * 
     * @param include
     */
    private void resolveInclude(IntroInclude include) {
        AbstractIntroElement target = findIncludeTarget(include);
        if (target == null)
            // target could not be found.
            return;
        if (target.isOfType(AbstractIntroElement.GROUP
                | AbstractIntroElement.ABSTRACT_TEXT
                | AbstractIntroElement.IMAGE | AbstractIntroElement.TEXT
                | AbstractIntroElement.PAGE_TITLE))
            // be picky about model elements to include. Can not use
            // BASE_ELEMENT model class because pages can not be included.
            insertTarget(include, target);
    }

    /**
     * Filters the appropriate elements from the given Vector, according to the current
     * environment. For example, if one of the elements has a tag to filter for os=linux and
     * the os is win32, the element will not be returned in the resulting Vector.
     * 
     * @param unfiltered the unfiltered elements
     * @return a new Vector with elements filtered
     */
	private <T> Vector<T> filterChildren(Vector<T> unfiltered) {
		Vector<T> filtered = new Vector<>();
		Iterator<T> iter = unfiltered.iterator();
    	while (iter.hasNext()) {
			T element = iter.next();
    		if (!UAContentFilter.isFiltered(element, IntroEvaluationContext.getContext())) {
        		filtered.add(element);
    		}
    	}
    	return filtered;
    }
    
    /**
     * Find the target element pointed to by the path in the include. It is
     * assumed that configId always points to an external config, and not the
     * same config of the inlcude.
     * 
     * @param include
     * @param path
     * @return
     */
    private AbstractIntroElement findIncludeTarget(IntroInclude include) {
        String path = include.getPath();
        IntroModelRoot targetModelRoot = (IntroModelRoot) getParentPage()
            .getParent();
        String targetConfigID = include.getConfigId();
        if (targetConfigID != null)
            targetModelRoot = ExtensionPointManager.getInst().getModel(
                targetConfigID);
        if (targetModelRoot == null)
            // if the target config was not found, skip this include.
            return null;
        AbstractIntroElement target = findTarget(targetModelRoot, path);
        return target;
    }

    /**
     * Finds the child element that corresponds to the given path in the passed
     * model.<br>
     * ps: This method could be a static method, but left as instance for model
     * enhancements.
     * 
     * @param model
     * @param path
     * @return
     */
    public AbstractIntroElement findTarget(AbstractIntroContainer container,
            String path) {
        // extract path segments. Get first segment to start search.
        String[] pathSegments = StringUtil.split(path, "/"); //$NON-NLS-1$
        if (container == null)
            return null;
        
        AbstractIntroElement target = container.findChild(pathSegments[0]);
        if (target == null)
            // there is no direct child with the specified first path segment.
            return null;

        // found parent segment. now find each child segment.
        for (int i = 1; i < pathSegments.length; i++) {
        	if (!(target instanceof AbstractIntroContainer)) {
                // parent is not a container, so no point going on.
        		return null;
        	}
            String pathSegment = pathSegments[i];
            target = ((AbstractIntroContainer) target).findChild(pathSegment);
            if (target == null)
                // tried to find next segment and failed.
                return null;
        }
        return target;
    }
    
    public AbstractIntroElement findTarget(AbstractIntroContainer container,
            String path, String extensionId) {
        // resolve path segments if they are incomplete.
        if (path.indexOf("@")!= -1) { //$NON-NLS-1$
        	// new in 3.2: dynamic resolution of incomplete target paths
        	IntroModelRoot root = getModelRoot();
        	if (root!=null) {
        		path = root.resolvePath(extensionId, path);
        		if (path==null)
        			return null;
        	}

        }
        return this.findTarget(container, path);
    }


    public AbstractIntroElement findTarget(String path) {
        return findTarget(this, path);
    }



    /*
     * searches direct children for the first child with the given id. The type
     * of the child can be any model element that has an id. ie:
     * AbstractIntroIdElement
     * 
     * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
     */
    public AbstractIntroElement findChild(String elementId) {
        return findChild(elementId, ID_ELEMENT);
    }

    /*
     * searches direct children for the first child with the given id. The type
     * of the child must be of the passed model types mask. This method handles
     * the 3.0 style model for content. Pages enhance this behavior with DOM
     * apis.
     * 
     * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
     */
    public AbstractIntroElement findChild(String elementId, int elementMask) {
        if (!loaded)
            loadChildren();

        for (int i = 0; i < children.size(); i++) {
            AbstractIntroElement aChild = (AbstractIntroElement) children
                .elementAt(i);
            if (!aChild.isOfType(ID_ELEMENT))
                // includes and heads do not have ids, and so can not be
                // referenced directly. This means that they can not be
                // targets for other includes. Skip, just in case someone
                // adds an id to it! Also, this applies to all elements in
                // the model that do not have ids.
                continue;
            AbstractIntroIdElement child = (AbstractIntroIdElement) aChild;
            if (child.getId() != null && child.getId().equals(elementId)
                    && child.isOfType(elementMask))
                return child;
        }
        // no child with given id and type found.
        return null;
    }



    private void insertTarget(IntroInclude include, AbstractIntroElement target) {
        int includeLocation = children.indexOf(include);
        if (includeLocation == -1)
            // should never be here.
            return;
        children.remove(includeLocation);
        // handle merging target styles first, before changing target parent to
        // enable inheritance of styles.
        handleIncludeStyleInheritence(include, target);
        // now clone the target node because original model should be kept
        // intact.
        AbstractIntroElement clonedTarget = null;
        try {
            clonedTarget = (AbstractIntroElement) target.clone();
        } catch (CloneNotSupportedException ex) {
            // should never be here.
            Log.error("Failed to clone Intro model node.", ex); //$NON-NLS-1$
            return;
        }
        // set parent of cloned target to be this container.
        clonedTarget.setParent(this);
        children.insertElementAt(clonedTarget, includeLocation);
    }

    /**
     * Updates the inherited styles based on the merge-style attribute. If we
     * are including a shared group, or if we are including an element from the
     * same page, do nothing. For inherited alt-styles, we have to cache the pd
     * from which we inherited the styles to be able to access resources in that
     * plugin. Also note that when including a container, it must be resolved
     * otherwise reparenting will cause includes in this target container to
     * fail.
     * 
     * @param include
     * @param target
     */
    private void handleIncludeStyleInheritence(IntroInclude include,
            AbstractIntroElement target) {

        if (include.getMergeStyle() == false)
            // target styles are not needed. nothing to do.
            return;

        if (target.getParent().getType() == AbstractIntroElement.MODEL_ROOT
                || target.getParentPage().equals(include.getParentPage()))
            // If we are including from this same page ie: target is in the
            // same page, OR if we are including a shared group, defined
            // under a config, do not include styles.
            return;

        // Update the parent page styles. skip style if it is null. Note,
        // include both the target page styles and inherited styles. The full
        // page styles need to be include.
        String style = target.getParentPage().getStyle();
        if (style != null)
            getParentPage().addStyle(style);

        // for alt-style cache bundle for loading resources.
        style = target.getParentPage().getAltStyle();
        if (style != null) {
            Bundle bundle = target.getBundle();
            getParentPage().addAltStyle(style, bundle);
        }

        // now add inherited styles. Race condition could happen here if Page A
        // is including from Page B which is in turn including from Page A.
        getParentPage().addStyles(target.getParentPage().getStyles());
        getParentPage().addAltStyles(target.getParentPage().getAltStyles());

    }

    /**
     * Creates a clone of the given target node. A clone is create by simply
     * recreating that protion of the model.
     * 
     * Note: looked into the clonable interface in Java, but it was not used
     * because it makes modifications/additions to the model harder to maintain.
     * Will revisit later.
     * 
     * @param targer
     * @return
     */
    protected AbstractIntroElement cloneTarget(AbstractIntroElement target) {
        return null;
    }

    @Override
	public int getType() {
        return AbstractIntroElement.ABSTRACT_CONTAINER;
    }

    /**
     * Deep copy since class has mutable objects. Leave DOM element as a shallow
     * reference copy since DOM is immutable.
     */
    @Override
	public Object clone() throws CloneNotSupportedException {
        AbstractIntroContainer clone = (AbstractIntroContainer) super.clone();
		clone.children = new Vector<>();
        if (children != null) {
            for (int i = 0; i < children.size(); i++) {
                AbstractIntroElement cloneChild = (AbstractIntroElement) ((AbstractIntroElement) children
                    .elementAt(i)).clone();
                cloneChild.setParent(clone);
                clone.children.add(i, cloneChild);
            }
        }
        return clone;
    }

    /**
     * Returns the element.
     * 
     * @return
     */
    @Override
	public Element getElement() {
        return this.element;
    }

    public String getBase() {
        return base;
    }


    /*
     * Clears this container. This means emptying the children, and resetting
     * flags.
     */
    public void clearChildren() {
        this.children.clear();
    }


    /**
     * Adds a model element as a child. Caller is responsible for inserting
     * model elements that rea valid as children.
     * 
     * @param child
     */
    public void addChild(AbstractIntroElement child) {
        children.add(child);
    }
    
    public void removeChild(AbstractIntroElement child) {
    	children.remove(child);
    }

	public String getBackgroundImage() {
		return getAttribute(element, ATT_BG_IMAGE);
	}


}

Back to the top