Skip to main content
summaryrefslogtreecommitdiffstats
blob: 34e6d875dc618c6b3a07e87e6a39f4a13d137b7c (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
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
/*******************************************************************************
 * Copyright (c) 2004, 2017 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.help.UAContentFilter;
import org.eclipse.help.internal.UAElementFactory;
import org.eclipse.help.internal.util.ProductPreferences;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.internal.intro.impl.FontSelection;
import org.eclipse.ui.internal.intro.impl.IntroPlugin;
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.IntroEvaluationContext;
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$
    private static final String VAR_THEME = "theme";  //$NON-NLS-1$
    private static final String VAR_DIRECTION = "direction";  //$NON-NLS-1$

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

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

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

    // a list to hold all loaded DOMs until resolving all configExtensions
    // is done.
	private List<ExtensionContent> unresolvedConfigExt = new ArrayList<>();


    private class ExtensionContent {
    	Element element;
        IConfigurationElement configExtElement;
        ExtensionContent(Element element,
                IConfigurationElement configExtElement) {
            this.element = element;
            this.configExtElement = configExtElement;
        }
    }

    /**
     * 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();
        determineHomePage();
    }

    /**
     * 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.
     *
     */
    @Override
	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);
            Log.warning("Could not find presentation element in intro config."); //$NON-NLS-1$
            return;
        }

        loadTheme();
        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);
            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);

		if (configurer != null) {
			// The configurer may vary its returned results based on the theme
			// properties
			configurer.bind(this);
		}
    }

    /**
     * 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.
     */
    @Override
	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$
    		}
    	}
    }

    private void determineHomePage() {
    	String pid = Platform.getProduct().getId();
    	startPageId = getProcessPreference("INTRO_START_PAGE", pid); //$NON-NLS-1$
    	String homePagePreference = getProcessPreference("INTRO_HOME_PAGE", pid); //$NON-NLS-1$
    	homePage = rootPage;  // Default, may be overridden
    	if (homePagePreference.length() != 0) {
    		AbstractIntroPage page = (AbstractIntroPage) findChild(homePagePreference,
    	            ABSTRACT_PAGE);
    		if (page != null) {
    			homePage = page;
    		    if(startPageId.length() == 0) {
    			    startPageId = homePagePreference;
    		    }
    		}
    	}
    	String standbyPagePreference = getProcessPreference("INTRO_STANDBY_PAGE", pid); //$NON-NLS-1$
        modelStandbyPageId = getPresentation().getStandbyPageId();

        if (standbyPagePreference.length() != 0) {
        	standbyPage = (AbstractIntroPage) findChild(standbyPagePreference,
    	            ABSTRACT_PAGE);
        }
        if (standbyPage == null && modelStandbyPageId != null && modelStandbyPageId.length() != 0) {
        	standbyPage = (AbstractIntroPage) findChild(modelStandbyPageId,
    	            ABSTRACT_PAGE);
        }
        if (standbyPage != null) {
            standbyPage.setStandbyPage(true);
        }
    }

    private void loadTheme() {
    	String pid = Platform.getProduct().getId();
    	String themeId = getProcessPreference("INTRO_THEME", pid); //$NON-NLS-1$

    	IConfigurationElement [] elements = Platform.getExtensionRegistry().getConfigurationElementsFor("org.eclipse.ui.intro.configExtension"); //$NON-NLS-1$
    	IConfigurationElement themeElement=null;
    	for (int i=0; i<elements.length; i++) {
    		if (elements[i].getName().equals("theme")) { //$NON-NLS-1$
    			String id = elements[i].getAttribute("id"); //$NON-NLS-1$
    			if (themeId!=null) {
    				if (id!=null && themeId.equals(id)) {
    					// use this one
    					themeElement = elements[i];
    					break;
    				}
    			}
    			else {
    				// see if this one is the default
    				String value = elements[i].getAttribute("default"); //$NON-NLS-1$
    				if (value!=null && value.equalsIgnoreCase("true")) { //$NON-NLS-1$
    					themeElement = elements[i];
    					break;
    				}
    			}
    		}
    	}
    	if (themeElement!=null) {
    		theme = new IntroTheme(themeElement);
    	}
    }

    /**
     * Loads all pages defined in this config from the xml content file.
     */
    private void loadPages(Document dom, Bundle bundle) {
        String rootPageId = getPresentation().getHomePageId();
        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(
                rootPageId)) {
                // Create the model class for the Root Page.
                rootPage = new IntroHomePage(pageElement, bundle, base);
                rootPage.setParent(this);
                currentPageId = rootPage.getId();
                children.add(rootPage);
            } 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++) {
            processConfigExtension(configExtensionElements[i]);
        }

        tryResolvingExtensions();

        // At this stage all pages will be resolved, some contributions may not be

        // now add all unresolved extensions as model children and log fact.
        Iterator keys = unresolvedConfigExt.iterator();
        while (keys.hasNext()) {
            ExtensionContent extension = (ExtensionContent) keys.next();
            Element configExtensionElement = extension.element;
            IConfigurationElement configExtConfigurationElement = extension.configExtElement;
            Bundle bundle = BundleUtil
                .getBundleFromConfigurationElement(configExtConfigurationElement);
            String base = getBase(configExtConfigurationElement);
            children.add(new IntroExtensionContent(configExtensionElement,
                bundle, base, configExtConfigurationElement));

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

    private void processConfigExtension(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;
        processConfigExtension(dom, configExtElement);
    }


    private void processConfigExtension(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[] extensionContentElements = loadExtensionContent(dom,
            configExtElement, base);
        for (int i = 0; i < extensionContentElements.length; i++) {
        	Element extensionContentElement = extensionContentElements[i];
             unresolvedConfigExt.add(new ExtensionContent(extensionContentElement,
                         configExtElement));
        }

        // Now load all pages and shared groups
        // from this config extension. 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.
        	if (!UAContentFilter.isFiltered(UAElementFactory.newElement(pages[j]), IntroEvaluationContext.getContext())) {
                IntroPage page = new IntroPage(pages[j], bundle, base);
                page.setParent(this);
                children.add(page);
        	}
        }
    }

    private void tryResolvingExtensions() {
    	int previousSize;
    	do {
    		previousSize = unresolvedConfigExt.size();
			List<ExtensionContent> stillUnresolved = new ArrayList<>();
			for (ExtensionContent content : unresolvedConfigExt) {
                Element extensionContentElement = content.element;
                IConfigurationElement configExtElement = content.configExtElement;
				Bundle bundle = BundleUtil.getBundleFromConfigurationElement(configExtElement);
				String elementBase = getBase(configExtElement);
                processOneExtension(configExtElement, elementBase, bundle, extensionContentElement);
				if (extensionContentElement.hasAttribute("failed")) { //$NON-NLS-1$
					stillUnresolved.add(content);
				}
            }
            unresolvedConfigExt = stillUnresolved;
        } while (unresolvedConfigExt.size() < previousSize);
    }

    /**
     * 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.
		List<Element> elements = new ArrayList<>();
        Element[] extensionContents = ModelUtil.getElementsByTagName(dom,
            IntroExtensionContent.TAG_CONTAINER_EXTENSION);
        Element[] replacementContents =	 ModelUtil.getElementsByTagName(dom,
        			IntroExtensionContent.TAG_CONTAINER_REPLACE);

        addUnfilteredExtensions(elements, extensionContents);
        addUnfilteredExtensions(elements, replacementContents);

        return elements.toArray(new Element[elements.size()]);
    }

	private void addUnfilteredExtensions(List<Element> elements, Element[] extensionContents) {
		for (int i = 0; i < extensionContents.length; i++) {
        	Element extensionContentElement = extensionContents[i];
        	if (!UAContentFilter.isFiltered(UAElementFactory.newElement(extensionContentElement), IntroEvaluationContext.getContext())) {
        	    elements.add(extensionContentElement);
            }
        }
	}

	private void processOneExtension(IConfigurationElement configExtElement, String base, Bundle bundle,
			Element extensionContentElement) {
		// Create the model class for extension content.
		IntroExtensionContent extensionContent = new IntroExtensionContent(
		    extensionContentElement, bundle, base, configExtElement);
		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$
	}


    /**
     * 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;

        // Insert all children of this extension before the target element. 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 targetElement = targetPage.findDomChild(pathSegments[1], "*"); //$NON-NLS-1$
        if (targetElement == null)
            return false;

        // get extension content
        Element[] elements = extensionContent.getElements();
        // insert all children before anchor in page body.
        for (int i = 0; i < elements.length; i++) {
            Node targetNode = pageDom.importNode(elements[i], true);
            // update the src attribute of this node, if defined by w3
            // specs.

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

        if (extensionContent.getExtensionType() == IntroExtensionContent.TYPE_REPLACEMENT) {
            targetElement.getParentNode().removeChild(targetElement);
        }

        // 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();
        int type = extensionContent.getExtensionType();
        AbstractIntroElement target = findTarget(this, path, extensionContent.getId());
        if (target != null && target.isOfType(AbstractIntroElement.ANCHOR) == (type == IntroExtensionContent.TYPE_CONTRIBUTION)) {
        	// insert all children of this extension before the target element/anchor.
	        insertExtensionChildren(target, extensionContent, extensionContent.getBundle(), extensionContent.getBase());
	        // anchors need to stay around to receive other contributions
	        if (type == IntroExtensionContent.TYPE_REPLACEMENT) {
	        	AbstractIntroContainer parent = (AbstractIntroContainer)target.getParent();
	        	parent.removeChild(target);
	        }
	        handleExtensionStyleInheritence(target, extensionContent);
	        return true;
        }
        // appropriate target could not be found. Signal failure.
        return false;
    }

    private void insertExtensionChildren(AbstractIntroElement target,
            IntroExtensionContent extensionContent, Bundle bundle, String base) {
        AbstractIntroContainer parent = (AbstractIntroContainer)target.getParent();
        // insert the elements of the extension before the target
        String mixinStyle = getMixinStyle(extensionContent);
        Element [] children = extensionContent.getChildren();
        parent.insertElementsBefore(children, bundle, base, target, mixinStyle);
    }

    private String getMixinStyle(IntroExtensionContent extensionContent) {
    	String path = extensionContent.getPath();
    	if (!path.endsWith("/@")) //$NON-NLS-1$
    		return null;
    	String pageId = path.substring(0, path.length()-2);
    	IntroModelRoot modelRoot = getModelRoot();
    	if (modelRoot==null)
    		return null;
    	IntroConfigurer configurer = modelRoot.getConfigurer();
    	if (configurer==null)
    		return null;
    	String extensionId = extensionContent.getId();
    	// if this is a replace, take the mixin style as what is being replaced
    	if (extensionContent.getExtensionType() == IntroExtensionContent.TYPE_REPLACEMENT) {
    		IPath ipath = new Path(extensionContent.getPath());
    		String s2 = ipath.segment(1);
    		if (s2 != null && s2.startsWith("@") && s2.length() > 1) { //$NON-NLS-1$
    			extensionId = s2.substring(1);
    		}
    	}
   		return configurer.getMixinStyle(pageId, extensionId);
    }


    /**
     * Updates the inherited styles based on the style attribtes defined in the
     * configExtension. 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(AbstractIntroElement target,
            IntroExtensionContent extension) {

        AbstractIntroContainer targetContainer = (AbstractIntroContainer)target.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.
		Map<String, Bundle> altStyles = extension.getAltStyles();
        if (altStyles != null)
            targetContainer.getParentPage().addAltStyles(altStyles);
    }

    /**
     * Sets the model state based on all the model classes.
     */
    private void setModelState(boolean loaded, boolean hasValidConfig) {
        this.loaded = loaded;
        this.hasValidConfig = hasValidConfig;
    }

    /**
     * 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 home Page.
     */
    public AbstractIntroPage getHomePage() {
        return homePage;
    }

    /**
     * @return Returns the root Page.
     */
    public IntroHomePage getRootPage() {
        return rootPage;
    }

    /**
     * @return Returns the standby Page. May return null if standby page is not
     *         defined.
     */
    public AbstractIntroPage 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() {
        if ("swt".equals(getPresentation().getImplementationKind())) { //$NON-NLS-1$
        	return rootPage != null && rootPage.isDynamic();
        }
        return true;
    }

    /**
     * @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(rootPage.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() {

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

                @Override
				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 (!isDynamic())
            return null;
        AbstractIntroPage page = (AbstractIntroPage) findChild(currentPageId,
            ABSTRACT_PAGE);
        if (page != null)
            return page;
        // not a page. Test for root page.
        if (currentPageId.equals(rootPage.getId()))
            return rootPage;
        // return null if page is not found.
        return null;
    }

    @Override
	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.ensureFileURLsExist(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 (text==null) return null;
    	if (text.indexOf('$')== -1)
    		return text;
    	// resolve
    	boolean inVariable=false;
    	StringBuilder buf = new StringBuilder();
    	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 = getVariableValue(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();
    }

    private String getVariableValue(String variable) {
     	if (variable.equals(VAR_THEME)) {
    		if (theme!=null)
    			return theme.getPath();
    	}
     	if (variable.equals(FontSelection.VAR_FONT_STYLE)) {
     		return FontSelection.getFontStyle();
    	}
     	if (variable.equals(VAR_DIRECTION)) {
    		if (ProductPreferences.isRTL()) {
    			return "rtl"; //$NON-NLS-1$
    		} else {
    			return "ltr"; //$NON-NLS-1$
    		}
    	}

    	if (configurer!=null)
    		return configurer.getVariable(variable);
    	return null;
    }

    public String resolvePath(String extensionId, String path) {
    	if (configurer==null) return null;
    	return configurer.resolvePath(extensionId, path);
    }


	public IntroTheme getTheme() {
		return theme;
	}

	public String getStartPageId() {
		return startPageId;
	}

	private String getProcessPreference(String key, String pid) {
		String result = Platform.getPreferencesService().getString
		      (IntroPlugin.PLUGIN_ID,  pid + '_' + key, "", null); //$NON-NLS-1$
    	if (result.length() == 0) {
    		result = Platform.getPreferencesService().getString
		        (IntroPlugin.PLUGIN_ID,  key, "", null); //$NON-NLS-1$
    	}
    	return result;
	}
}

Back to the top