Skip to main content
summaryrefslogtreecommitdiffstats
blob: 7ba34ff6cb12bb0c1d5cc1ac81ec2b2b3f6ea35e (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
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
/*******************************************************************************
 * Copyright (c) 2004, 2005 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.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.internal.intro.impl.model.loader.IntroContentParser;
import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
import org.eclipse.ui.internal.intro.impl.model.util.BundleUtil;
import org.eclipse.ui.internal.intro.impl.model.util.ModelUtil;
import org.eclipse.ui.internal.intro.impl.util.Log;
import org.eclipse.ui.intro.config.IntroConfigurer;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * The root class for the OOBE model. It loads the configuration into the
 * appropriate classes.
 * 
 * Model rules:
 * <ol>
 * <li>if an attribute is not included in the markup, its value will be null in
 * the model.</li>
 * <li>Resources in plugin.xml are not implicitly resolved against $nl$.
 * Resources in pages are implicitly resolved against $nl$
 * <li>the current page id is set silently when loading the model. You do not
 * need the event notification on model load.</li>
 * <li>Children of a given parent (ie: model root, page, or group) *must* have
 * distinctive IDs otherwise resolving includes and extensions may fail.</li>
 * <li>Containers have the concept of loading children and resolving children.
 * At the model root level, resolving children means resolving ALL extensions of
 * model. At the container level, resolving children means resolving includes.
 * </li>
 * <li>Extensions are resolved before includes at the container level to avoid
 * race conditions. eg: if a page includes a shared group and an extension
 * extends this shared group, you want the include to get the extended group and
 * not the original group.</li>
 * <li>Resolving extensions should not resolve includes. No need to load other
 * models when we dont have to. Plus, extensions can only reference anchors, and
 * so no need to resolve includes.</li>
 * <li>Extensions can not target containers *after* they are resolved. For
 * example, an extension can not target a shared group after it has been
 * included in a page. It can target the initial shared group as a path, but not
 * the group in the page as a path. Again this is because extensions extends
 * anchors that already have a path, not a resolved path.</li>
 * <li>Pages and shared groups that are contributed through extensions become
 * children of the atrget configuration, and so any includes they may have will
 * be resolved correctly.</li>
 * <li>An infinite loop can occur if page A includes from page B and page B in
 * turn includes from page A. ie: cyclic includes. For performnace, accept.
 * </li>
 * <li>When resolving includes, if the target is a container, it must be
 * resolved to resolve its includes correctly. Otherwise, included includes will
 * fail due to reparenting.</li>
 * <li>unresolved includes are left as children of the parent container.</li>
 * <li>Unresolved extensions are left as children of the targetted model.</li>
 * <li>For dynamic awarness, the model is nulled and then reloaded. However, we
 * need to preserve the presentation instance since the UI is already loaded.
 * This is done by reloading the model, and directly resetting the presentation
 * to what it was.</li>
 * <li>Model classes should not have DOM classes as instance vars, and if this
 * is a must, null the DOM class instance the minute you are done. This is
 * because you want the VM to garbage collect the DOM model. Keeping a reference
 * to the DOM model from the Intro model will prevent that.</li>
 * </ol>
 * <li>(since 3.0.2) several passes are used to resolve contributions to
 * anchors that themselves where contributed through an extension. Each time a
 * contribution is resolved, the model tries to resolve all unresolved
 * contribution, recursively.
 * </ul>
 */
public class IntroModelRoot extends AbstractIntroContainer {

    /**
     * Model constants that fire property change event when they are changed in
     * the model.
     */
    public static final int CURRENT_PAGE_PROPERTY_ID = 1;

    private static final String ATT_CONTENT = "content"; //$NON-NLS-1$
    private static final String ATT_CONFIGURER = "configurer"; //$NON-NLS-1$

    // False if there is no valid contribution to the
    // org.eclipse.ui.into.config extension point. Start off with true, and set
    // to false whenever something bad happens.
    private boolean hasValidConfig = true;
    private boolean isdynamicIntro;
    private IntroConfigurer configurer;
    private IntroPartPresentation introPartPresentation;
    private IntroHomePage homePage;
    private String currentPageId;
    private IntroHomePage standbyPage;

    // the config extensions for this model.
    private IConfigurationElement[] configExtensionElements;

    // maintain listener list for model changes.
    public ListenerList propChangeListeners = new ListenerList();

    // a hashtable to hold all loaded DOMs until resolving all configExtensions
    // is done. Key is one extensionContent DOM element, while value is the
    // IConfigurationElement from where it was loaded. This is needed to extract
    // the base for the xml content file.
    private Hashtable unresolvedConfigExt = new Hashtable();


    /**
     * Model root. Takes a configElement that represents <config>in the
     * plugin.xml markup AND all the extension contributed to this model through
     * the configExtension point.
     */
    public IntroModelRoot(IConfigurationElement configElement,
            IConfigurationElement[] configExtensionElements) {
        // the config element that represents the correct model root.
        super(configElement);
        this.configExtensionElements = configExtensionElements;

    }

    public void loadModel() {
        getChildren();
    }

    /**
     * Loads the full model. The children of a model root are the presentation,
     * followed by all pages, and all shared groups. Then if the model has
     * extension, its the unresolved container extensions, followed by all
     * extension pages and groups. The presentation is loaded from the
     * IConfiguration element representing the config. All else is loaded from
     * xml content file.
     * 
     */
    protected void loadChildren() {
        children = new Vector();
        if (Log.logInfo)
            Log.info("Creating Intro plugin model...."); //$NON-NLS-1$

        // load presentation first and create the model class for it. If there
        // is more than one presentation, load first one, and log rest.
        IConfigurationElement presentationElement = loadPresentation();
        if (presentationElement == null) {
            // no presentations at all, exit.
            setModelState(true, false, false);
            Log.warning("Could not find presentation element in intro config."); //$NON-NLS-1$
            return;
        }
        
        loadConfigurer();

        introPartPresentation = new IntroPartPresentation(presentationElement);
        children.add(introPartPresentation);
        // set parent.
        introPartPresentation.setParent(this);

        // now load all children of the config. There should only be pages and
        // groups here. And order is not important. These elements are loaded
        // from the content file DOM.
        Document document = loadDOM(getCfgElement());
        if (document == null) {
            // we failed to parse the content file. Intro Parser would have
            // logged the fact. Parser would also have checked to see if the
            // content file has the correct root tag.
            setModelState(true, false, false);
            return;
        }

        // set base for this model.
        this.base = getBase(getCfgElement());

        // now load content.
        loadPages(document, getBundle());
        loadSharedGroups(document, getBundle());

        // Attributes of root page decide if we have a static or dynamic case.
        setModelState(true, true, getHomePage().isDynamic());
    }

    /**
     * Sets the presentation to the given presentation. The model always has the
     * presentation as the first child, so use that fact. This method is used
     * for dynamic awarness to enable replacing the new presentation with the
     * existing one after a model refresh.
     * 
     * @param presentation
     */
    public void setPresentation(IntroPartPresentation presentation) {
        this.introPartPresentation = presentation;
        presentation.setParent(this);
        children.set(0, presentation);
    }

    /**
     * Resolve contributions into this container's children.
     */
    protected void resolveChildren() {
        // now handle config extension.
        resolveConfigExtensions();
        resolved = true;
    }

    private IConfigurationElement loadPresentation() {
        // If there is more than one presentation, load first one, and log
        // rest.
        IConfigurationElement[] presentationElements = getCfgElement()
            .getChildren(IntroPartPresentation.TAG_PRESENTATION);

        IConfigurationElement presentationElement = ModelLoaderUtil
            .validateSingleContribution(presentationElements,
                IntroPartPresentation.ATT_HOME_PAGE_ID);
        return presentationElement;
    }
    
    private void loadConfigurer() {
    	String cname = getCfgElement().getAttribute(ATT_CONFIGURER);
    	if (cname!=null) {
    		try {
    			Object obj = getCfgElement().createExecutableExtension(ATT_CONFIGURER);
    			if (obj instanceof IntroConfigurer)
    				configurer = (IntroConfigurer)obj;
    		}
    		catch (CoreException e) {
    			Log.error("Error loading intro configurer", e); //$NON-NLS-1$
    		}
    	}
    }

    /**
     * Loads all pages defined in this config from the xml content file.
     */
    private void loadPages(Document dom, Bundle bundle) {
        String homePageId = getPresentation().getHomePageId();
        String standbyPageId = getPresentation().getStandbyPageId();
        Element[] pages = ModelUtil.getElementsByTagName(dom,
            AbstractIntroPage.TAG_PAGE);
        for (int i = 0; i < pages.length; i++) {
            Element pageElement = pages[i];
            if (pageElement.getAttribute(AbstractIntroIdElement.ATT_ID).equals(
                homePageId)) {
                // Create the model class for the Root Page.
                homePage = new IntroHomePage(pageElement, bundle, base);
                homePage.setParent(this);
                currentPageId = homePage.getId();
                children.add(homePage);
            } else if (pageElement.getAttribute(AbstractIntroIdElement.ATT_ID)
                .equals(standbyPageId)) {
                // Create the model class for the standby Page.
                standbyPage = new IntroHomePage(pageElement, bundle, base);
                standbyPage.setParent(this);
                // signal that it is a standby page.
                standbyPage.setStandbyPage(true);
                children.add(standbyPage);
            } else {
                // Create the model class for an intro Page.
                IntroPage page = new IntroPage(pageElement, bundle, base);
                page.setParent(this);
                children.add(page);
            }
        }
    }

    /**
     * Loads all shared groups defined in this config, from the DOM.
     */
    private void loadSharedGroups(Document dom, Bundle bundle) {
        Element[] groups = ModelUtil.getElementsByTagName(dom,
            IntroGroup.TAG_GROUP);
        for (int i = 0; i < groups.length; i++) {
            IntroGroup group = new IntroGroup(groups[i], bundle, base);
            group.setParent(this);
            children.add(group);
        }
    }

    /**
     * Handles all the configExtensions to this current model. Resolving
     * configExts means finding target anchor and inserting extension content at
     * target. Also, several passes are used to resolve as many extensions as
     * possible. This allows for resolving nested anchors (ie: anchors to
     * anchors in contributions).
     */
    private void resolveConfigExtensions() {
        for (int i = 0; i < configExtensionElements.length; i++)
            resolveConfigExtension(configExtensionElements[i]);

        // now add all unresolved extensions as model children and log fact.
        Enumeration keys = unresolvedConfigExt.keys();
        while (keys.hasMoreElements()) {
            Element configExtensionElement = (Element) keys.nextElement();
            IConfigurationElement configExtConfigurationElement = (IConfigurationElement) unresolvedConfigExt
                .get(configExtensionElement);
            Bundle bundle = BundleUtil
                .getBundleFromConfigurationElement(configExtConfigurationElement);
            String base = getBase(configExtConfigurationElement);
            children.add(new IntroExtensionContent(configExtensionElement,
                bundle, base));

            // INTRO: fix log strings.
            Log
                .warning("Could not resolve the following configExtension: " //$NON-NLS-1$
                        + ModelLoaderUtil.getLogString(bundle,
                            configExtensionElement,
                            IntroExtensionContent.ATT_PATH));
        }
    }

    private void resolveConfigExtension(IConfigurationElement configExtElement) {
        // This call will extract the parent folder if needed.
        Document dom = loadDOM(configExtElement);
        if (dom == null)
            // we failed to parse the content file. Intro Parser would
            // have logged the fact. Parser would also have checked to
            // see if the content file has the correct root tag.
            return;
        resolveConfigExtension(dom, configExtElement);
    }


    private void resolveConfigExtension(Document dom,
            IConfigurationElement configExtElement) {

        // Find the target of this container extension, and add all its
        // children to target. Make sure to pass correct bundle and base to
        // propagate to all children.
        String base = getBase(configExtElement);
        Element extensionContentElement = loadExtensionContent(dom,
            configExtElement, base);
        if (extensionContentElement == null)
            // no extension content defined, ignore extension completely.
            return;

        if (extensionContentElement.hasAttribute("failed")) { //$NON-NLS-1$
            // we failed to resolve this configExtension, because target
            // could not be found or is not an anchor, add the extension to the
            // list of unresolved configExtensions.
            // INTRO: an extensionContent is used as a key, instead of the whole
            // DOM. This is usefull if we need to support multiple extension
            // contents in one file.
            if (!unresolvedConfigExt.containsKey(extensionContentElement))
                unresolvedConfigExt.put(extensionContentElement,
                    configExtElement);
            return;
        }

        // We resolved a contribution. Now load all pages and shared groups
        // from this config extension. No point adding pages that will never
        // be referenced. Get the bundle from the extensions since they are
        // defined in other plugins.
        Bundle bundle = BundleUtil
            .getBundleFromConfigurationElement(configExtElement);

        Element[] pages = ModelUtil.getElementsByTagName(dom,
            AbstractIntroPage.TAG_PAGE);
        for (int j = 0; j < pages.length; j++) {
            // Create the model class for an intro Page.
            IntroPage page = new IntroPage(pages[j], bundle, base);
            page.setParent(this);
            children.add(page);
        }

        // load all shared groups from all configExtensions to this model.
        loadSharedGroups(dom, bundle);

        // since we resolved a contribution, try resolving some of the
        // unresolved ones before going on.
        unresolvedConfigExt.remove(extensionContentElement);
        tryResolvingExtensions();
    }


    private void tryResolvingExtensions() {
        Enumeration keys = unresolvedConfigExt.keys();
        while (keys.hasMoreElements()) {
            Element extensionContentElement = (Element) keys.nextElement();
            resolveConfigExtension(extensionContentElement.getOwnerDocument(),
                (IConfigurationElement) unresolvedConfigExt
                    .get(extensionContentElement));
        }
    }


    /**
     * load the extension content of this configExtension into model classes,
     * and insert them at target. A config extension can have only ONE extension
     * content. This is because if the extension fails, we need to be able to
     * not include the page and group contributions as part of the model. If
     * extension content has XHTML content (ie: content attribute is defined) we
     * load extension DOM into target page dom.
     * 
     * note: the extension Element is returned to enable creating a child model
     * element on failure.
     * 
     * @param
     * @return
     */
    private Element loadExtensionContent(Document dom,
            IConfigurationElement configExtElement, String base) {

        // get the bundle from the extensions since they are defined in
        // other plugins.
        Bundle bundle = BundleUtil
            .getBundleFromConfigurationElement(configExtElement);

        Element[] extensionContents = ModelUtil.getElementsByTagName(dom,
            IntroExtensionContent.TAG_CONTAINER_EXTENSION);
        // INTRO: change this. we may need to load more than one extension
        // content here.
        // There should only be one container extension. (ver3.0)
        Element extensionContentElement = ModelLoaderUtil
            .validateSingleContribution(bundle, extensionContents,
                IntroExtensionContent.ATT_PATH);
        if (extensionContentElement == null)
            // no extensionContent defined.
            return null;

        // Create the model class for extension content.
        IntroExtensionContent extensionContent = new IntroExtensionContent(
            extensionContentElement, bundle, base);
        boolean success = false;
        if (extensionContent.isXHTMLContent())
            success = loadXHTMLExtensionContent(extensionContent);
        else
            success = load3_0ExtensionContent(extensionContent);

        if (success) {
            if (extensionContentElement.hasAttribute("failed")) //$NON-NLS-1$
                extensionContentElement.removeAttribute("failed"); //$NON-NLS-1$
        } else
            extensionContentElement.setAttribute("failed", "true"); //$NON-NLS-1$ //$NON-NLS-2$

        return extensionContentElement;
    }



    /**
     * Insert the extension content into the target.
     * 
     * @param extensionContent
     * @return
     */
    private boolean loadXHTMLExtensionContent(
            IntroExtensionContent extensionContent) {
        String path = extensionContent.getPath();
        // path must be pageId/anchorID in the case of anchors in XHTML pages.
        String[] pathSegments = path.split("/"); //$NON-NLS-1$
        if (pathSegments.length != 2)
            // path does not have correct format.
            return false;
        AbstractIntroPage targetPage = (AbstractIntroPage) findChild(
            pathSegments[0], ABSTRACT_PAGE);
        if (targetPage == null)
            // target could not be found. Signal failure.
            return false;

        // extensions are only for anchors. Insert all children of this
        // extension before the target anchor. Anchors need to stay in DOM ,
        // even after all extensions have been resolved, to enable other
        // plugins to contribute. Find the target node.
        Document pageDom = targetPage.getDocument();
        Element targetAnchor = targetPage.findDomChild(pathSegments[1],
            IntroAnchor.TAG_ANCHOR);
        if (targetAnchor == null)
            return false;

        // get extension content node.
        Document extensionDom = extensionContent.getDocument();
        if (extensionDom == null)
            return false;

        Element extensionBody = ModelUtil.getBodyElement(extensionDom);
        Element[] children = ModelUtil.getElementsByTagName(extensionBody, "*"); //$NON-NLS-1$
        // insert all children before anchor in page body.
        for (int i = 0; i < children.length; i++) {
            Node targetNode = pageDom.importNode(children[i], true);
            // update the src attribute of this node, if defined by w3
            // specs.

            ModelUtil.updateResourceAttributes((Element) targetNode,
                extensionContent);
            targetAnchor.getParentNode().insertBefore(targetNode, targetAnchor);
        }

        // now handle style inheritance.
        // Update the parent page styles. skip style if it is null;
        String[] styles = extensionContent.getStyles();
        if (styles != null) {
            for (int i = 0; i < styles.length; i++)
                ModelUtil.insertStyle(pageDom, styles[i]);
        }

        return true;

    }



    /**
     * Insert the extension content (3.0 format) into the target.
     * 
     * @param extensionContent
     * @return
     */
    private boolean load3_0ExtensionContent(
            IntroExtensionContent extensionContent) {
        String path = extensionContent.getPath();
        AbstractIntroElement target = findTarget(this, path, extensionContent.getId());
        if (target == null || !target.isOfType(AbstractIntroElement.ANCHOR))
            // target could not be found. Signal failure.
            return false;

        // extensions are only for anchors. Insert all children of this
        // extension before this anchor. Anchors need to stay as model
        // children, even after all extensions have been
        // resolved, to enable other plugins to contribute.
        IntroAnchor targetAnchor = (IntroAnchor) target;
        insertAnchorChildren(targetAnchor, extensionContent, extensionContent
            .getBundle(), extensionContent.getBase());
        handleExtensionStyleInheritence(targetAnchor, extensionContent);

        return true;

    }


    private void insertAnchorChildren(IntroAnchor anchor,
            IntroExtensionContent extensionContent, Bundle bundle, String base) {
        AbstractIntroContainer anchorParent = (AbstractIntroContainer) anchor
            .getParent();
        // insert the elements of the extension before the anchor.
        anchorParent.insertElementsBefore(extensionContent.getChildren(),
            bundle, base, anchor);
    }


    /**
     * Updates the inherited styles based on the style attribtes defined in the
     * confgiExtension. If we are extending a shared group do nothing. For
     * inherited alt-styles, we have to cache the bundle from which we inherited
     * the styles to be able to access resources in that plugin.
     * 
     * @param include
     * @param target
     */
    private void handleExtensionStyleInheritence(IntroAnchor anchor,
            IntroExtensionContent extension) {

        AbstractIntroContainer targetContainer = (AbstractIntroContainer) anchor
            .getParent();
        if (targetContainer.getType() == AbstractIntroElement.GROUP
                && targetContainer.getParent().getType() == AbstractIntroElement.MODEL_ROOT)
            // if we are extending a shared group, defined under a config, we
            // can not include styles.
            return;

        // Update the parent page styles. skip style if it is null;
        String[] styles = extension.getStyles();
        if (styles != null)
            targetContainer.getParentPage().addStyles(styles);

        // for alt-style cache bundle for loading resources.
        Hashtable altStyles = extension.getAltStyles();
        if (altStyles != null)
            targetContainer.getParentPage().addAltStyles(altStyles);

    }



    /**
     * Sets the model state based on all the model classes. Dynamic nature of
     * the model is always setto false when we fail to load model for any
     * reason.
     */
    private void setModelState(boolean loaded, boolean hasValidConfig,
            boolean isdynamicIntro) {
        this.loaded = loaded;
        this.hasValidConfig = hasValidConfig;
        this.isdynamicIntro = isdynamicIntro;
    }

    /**
     * Returns true if there is a valid contribution to
     * org.eclipse.ui.intro.config extension point, with a valid Presentation,
     * and pages.
     * 
     * @return Returns the hasValidConfig.
     */
    public boolean hasValidConfig() {
        return hasValidConfig;
    }

    /**
     * @return Returns the introPartPresentation.
     */
    public IntroPartPresentation getPresentation() {
        return introPartPresentation;
    }
    
    public IntroConfigurer getConfigurer() {
    	return configurer;
    }

    /**
     * @return Returns the rootPage.
     */
    public IntroHomePage getHomePage() {
        return homePage;
    }

    /**
     * @return Returns the standby Page. May return null if standby page is not
     *         defined.
     */
    public IntroHomePage getStandbyPage() {
        return standbyPage;
    }

    /**
     * @return all pages *excluding* the Home Page. If all pages are needed,
     *         call <code>(AbstractIntroPage[])
     *         getChildrenOfType(IntroElement.ABSTRACT_PAGE);</code>
     */
    public IntroPage[] getPages() {
        return (IntroPage[]) getChildrenOfType(AbstractIntroElement.PAGE);
    }

    /**
     * @return Returns the isdynamicIntro.
     */
    public boolean isDynamic() {
        return isdynamicIntro;
    }

    /**
     * @return Returns the currentPageId.
     */
    public String getCurrentPageId() {
        return currentPageId;
    }


    /**
     * Sets the current page. If the model does not have a page with the passed
     * id, the message is logged, and the model retains its old current page.
     * 
     * @param currentPageId
     *            The currentPageId to set. *
     * @param fireEvent
     *            flag to indicate if event notification is needed.
     * @return true if the model has a page with the passed id, false otherwise.
     *         If the method fails, the current page remains the same as the
     *         last state.
     */
    public boolean setCurrentPageId(String pageId, boolean fireEvent) {
        if (pageId.equals(currentPageId))
            // setting to the same page does nothing. Return true because we did
            // not actually fail. just a no op.
            return true;

        AbstractIntroPage page = (AbstractIntroPage) findChild(pageId,
            ABSTRACT_PAGE);
        if (page == null) {
            // not a page. Test for root page.
            if (!pageId.equals(homePage.getId())) {
                // not a page nor the home page.
                Log
                    .warning("Could not set current page to Intro page with id: " + pageId); //$NON-NLS-1$
                return false;
            }
        }

        currentPageId = pageId;
        if (fireEvent)
            firePropertyChange(CURRENT_PAGE_PROPERTY_ID);
        return true;
    }

    public boolean setCurrentPageId(String pageId) {
        return setCurrentPageId(pageId, true);
    }

    public void addPropertyListener(IPropertyListener l) {
        propChangeListeners.add(l);
    }

    /**
     * Fires a property changed event. Made public because it can be used to
     * trigger a UI refresh.
     * 
     * @param propertyId
     *            the id of the property that changed
     */
    public void firePropertyChange(final int propertyId) {
        Object[] array = propChangeListeners.getListeners();
        for (int i = 0; i < array.length; i++) {
            final IPropertyListener l = (IPropertyListener) array[i];
            SafeRunner.run(new SafeRunnable() {

                public void run() {
                    l.propertyChanged(this, propertyId);
                }

                public void handleException(Throwable e) {
                    super.handleException(e);
                    // If an unexpected exception happens, remove it
                    // to make sure the workbench keeps running.
                    propChangeListeners.remove(l);
                }
            });
        }
    }

    public void removePropertyListener(IPropertyListener l) {
        propChangeListeners.remove(l);
    }

    /**
     * @return Returns the currentPage. return null if page is not found, or if
     *         we are not in a dynamic intro mode.
     */
    public AbstractIntroPage getCurrentPage() {
        if (!isdynamicIntro)
            return null;

        AbstractIntroPage page = (AbstractIntroPage) findChild(currentPageId,
            ABSTRACT_PAGE);
        if (page != null)
            return page;
        // not a page. Test for root page.
        if (currentPageId.equals(homePage.getId()))
            return homePage;
        // return null if page is not found.
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
     */
    public int getType() {
        return AbstractIntroElement.MODEL_ROOT;
    }


    /**
     * Assumes that the passed config element has a "content" attribute. Reads
     * it and loads a DOM based on that attribute value. It does not explicitly
     * resolve the resource because this method only loads the introContent and
     * the configExt content files. ie: in plugin.xml. <br>
     * This method also sets the base attribute on the root element in the DOM
     * to enable resolving all resources relative to this DOM.
     * 
     * @return
     */
    protected Document loadDOM(IConfigurationElement cfgElement) {
        String content = cfgElement.getAttribute(ATT_CONTENT);

        // To support jarring, extract parent folder of where the intro content
        // file is. It is expected that all intro content is in that one parent
        // folder. This works for both content files and configExtension content
        // files.
        Bundle domBundle = BundleUtil
            .getBundleFromConfigurationElement(cfgElement);
        ModelUtil.extractParentFolder(domBundle, content);

        // Resolve.
        content = BundleUtil.getResourceLocation(content, cfgElement);
        Document document = new IntroContentParser(content).getDocument();

        return document;
    }


    private String getBase(IConfigurationElement configElement) {
        String content = configElement.getAttribute(ATT_CONTENT);
        return ModelUtil.getParentFolderToString(content);
    }
    
    public String resolveVariables(String text) {
    	if (configurer==null) return text;
    	if (text.indexOf('$')== -1)
    		return text;
    	// resolve
    	boolean inVariable=false;
    	StringBuffer buf = new StringBuffer();
    	int vindex=0;
    	for (int i=0; i<text.length(); i++) {
    		char c = text.charAt(i);
    		if (c=='$') {
    			if (!inVariable) {
    				inVariable=true;
    				vindex=i+1;
    				continue;
    			}
 				inVariable=false;
   				String variable=text.substring(vindex, i);
   				String value = configurer.getVariable(variable);
   				if (value==null)
   					value = "$"+variable+"$"; //$NON-NLS-1$ //$NON-NLS-2$
   				buf.append(value);
   				continue;
    		}
    		else if (!inVariable)
    			buf.append(c);
    	}
    	return buf.toString();
    }
    
    public String resolvePath(String extensionId, String path) {
    	if (configurer==null) return null;
    	return configurer.resolvePath(extensionId, path);
    }
}

Back to the top