001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.intro.impl.model;
011:
012: import java.util.Enumeration;
013: import java.util.Hashtable;
014: import java.util.Vector;
015:
016: import org.eclipse.core.runtime.CoreException;
017: import org.eclipse.core.runtime.IConfigurationElement;
018: import org.eclipse.core.runtime.IPath;
019: import org.eclipse.core.runtime.ListenerList;
020: import org.eclipse.core.runtime.Path;
021: import org.eclipse.core.runtime.Platform;
022: import org.eclipse.core.runtime.Preferences;
023: import org.eclipse.core.runtime.SafeRunner;
024: import org.eclipse.help.UAContentFilter;
025: import org.eclipse.help.internal.UAElementFactory;
026: import org.eclipse.jface.util.SafeRunnable;
027: import org.eclipse.ui.IPropertyListener;
028: import org.eclipse.ui.internal.intro.impl.IntroPlugin;
029: import org.eclipse.ui.internal.intro.impl.model.loader.IntroContentParser;
030: import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
031: import org.eclipse.ui.internal.intro.impl.model.util.BundleUtil;
032: import org.eclipse.ui.internal.intro.impl.model.util.ModelUtil;
033: import org.eclipse.ui.internal.intro.impl.util.IntroEvaluationContext;
034: import org.eclipse.ui.internal.intro.impl.util.Log;
035: import org.eclipse.ui.internal.intro.impl.util.StringUtil;
036: import org.eclipse.ui.intro.config.IntroConfigurer;
037: import org.osgi.framework.Bundle;
038: import org.w3c.dom.Document;
039: import org.w3c.dom.Element;
040: import org.w3c.dom.Node;
041:
042: /**
043: * The root class for the OOBE model. It loads the configuration into the
044: * appropriate classes.
045: *
046: * Model rules:
047: * <ol>
048: * <li>if an attribute is not included in the markup, its value will be null in
049: * the model.</li>
050: * <li>Resources in plugin.xml are not implicitly resolved against $nl$.
051: * Resources in pages are implicitly resolved against $nl$
052: * <li>the current page id is set silently when loading the model. You do not
053: * need the event notification on model load.</li>
054: * <li>Children of a given parent (ie: model root, page, or group) *must* have
055: * distinctive IDs otherwise resolving includes and extensions may fail.</li>
056: * <li>Containers have the concept of loading children and resolving children.
057: * At the model root level, resolving children means resolving ALL extensions of
058: * model. At the container level, resolving children means resolving includes.
059: * </li>
060: * <li>Extensions are resolved before includes at the container level to avoid
061: * race conditions. eg: if a page includes a shared group and an extension
062: * extends this shared group, you want the include to get the extended group and
063: * not the original group.</li>
064: * <li>Resolving extensions should not resolve includes. No need to load other
065: * models when we dont have to. Plus, extensions can only reference anchors, and
066: * so no need to resolve includes.</li>
067: * <li>Extensions can not target containers *after* they are resolved. For
068: * example, an extension can not target a shared group after it has been
069: * included in a page. It can target the initial shared group as a path, but not
070: * the group in the page as a path. Again this is because extensions extends
071: * anchors that already have a path, not a resolved path.</li>
072: * <li>Pages and shared groups that are contributed through extensions become
073: * children of the atrget configuration, and so any includes they may have will
074: * be resolved correctly.</li>
075: * <li>An infinite loop can occur if page A includes from page B and page B in
076: * turn includes from page A. ie: cyclic includes. For performnace, accept.
077: * </li>
078: * <li>When resolving includes, if the target is a container, it must be
079: * resolved to resolve its includes correctly. Otherwise, included includes will
080: * fail due to reparenting.</li>
081: * <li>unresolved includes are left as children of the parent container.</li>
082: * <li>Unresolved extensions are left as children of the targetted model.</li>
083: * <li>For dynamic awarness, the model is nulled and then reloaded. However, we
084: * need to preserve the presentation instance since the UI is already loaded.
085: * This is done by reloading the model, and directly resetting the presentation
086: * to what it was.</li>
087: * <li>Model classes should not have DOM classes as instance vars, and if this
088: * is a must, null the DOM class instance the minute you are done. This is
089: * because you want the VM to garbage collect the DOM model. Keeping a reference
090: * to the DOM model from the Intro model will prevent that.</li>
091: * </ol>
092: * <li>(since 3.0.2) several passes are used to resolve contributions to
093: * anchors that themselves where contributed through an extension. Each time a
094: * contribution is resolved, the model tries to resolve all unresolved
095: * contribution, recursively.
096: * </ul>
097: */
098: public class IntroModelRoot extends AbstractIntroContainer {
099:
100: /**
101: * Model constants that fire property change event when they are changed in
102: * the model.
103: */
104: public static final int CURRENT_PAGE_PROPERTY_ID = 1;
105:
106: private static final String ATT_CONTENT = "content"; //$NON-NLS-1$
107: private static final String ATT_CONFIGURER = "configurer"; //$NON-NLS-1$
108: private static final String VAR_THEME = "theme"; //$NON-NLS-1$
109:
110: // False if there is no valid contribution to the
111: // org.eclipse.ui.into.config extension point. Start off with true, and set
112: // to false whenever something bad happens.
113: private boolean hasValidConfig = true;
114: private boolean isdynamicIntro;
115: private IntroConfigurer configurer;
116: private IntroTheme theme;
117: private IntroPartPresentation introPartPresentation;
118: private IntroHomePage homePage;
119: private String currentPageId;
120: private IntroHomePage standbyPage;
121:
122: // the config extensions for this model.
123: private IConfigurationElement[] configExtensionElements;
124:
125: // maintain listener list for model changes.
126: public ListenerList propChangeListeners = new ListenerList();
127:
128: // a hashtable to hold all loaded DOMs until resolving all configExtensions
129: // is done. Key is one extensionContent DOM element, while value is the
130: // IConfigurationElement from where it was loaded. This is needed to extract
131: // the base for the xml content file.
132: private Hashtable unresolvedConfigExt = new Hashtable();
133:
134: /**
135: * Model root. Takes a configElement that represents <config>in the
136: * plugin.xml markup AND all the extension contributed to this model through
137: * the configExtension point.
138: */
139: public IntroModelRoot(IConfigurationElement configElement,
140: IConfigurationElement[] configExtensionElements) {
141: // the config element that represents the correct model root.
142: super (configElement);
143: this .configExtensionElements = configExtensionElements;
144:
145: }
146:
147: public void loadModel() {
148: getChildren();
149: }
150:
151: /**
152: * Loads the full model. The children of a model root are the presentation,
153: * followed by all pages, and all shared groups. Then if the model has
154: * extension, its the unresolved container extensions, followed by all
155: * extension pages and groups. The presentation is loaded from the
156: * IConfiguration element representing the config. All else is loaded from
157: * xml content file.
158: *
159: */
160: protected void loadChildren() {
161: children = new Vector();
162: if (Log.logInfo)
163: Log.info("Creating Intro plugin model...."); //$NON-NLS-1$
164:
165: // load presentation first and create the model class for it. If there
166: // is more than one presentation, load first one, and log rest.
167: IConfigurationElement presentationElement = loadPresentation();
168: if (presentationElement == null) {
169: // no presentations at all, exit.
170: setModelState(true, false, false);
171: Log
172: .warning("Could not find presentation element in intro config."); //$NON-NLS-1$
173: return;
174: }
175:
176: loadTheme();
177: loadConfigurer();
178:
179: introPartPresentation = new IntroPartPresentation(
180: presentationElement);
181: children.add(introPartPresentation);
182: // set parent.
183: introPartPresentation.setParent(this );
184:
185: // now load all children of the config. There should only be pages and
186: // groups here. And order is not important. These elements are loaded
187: // from the content file DOM.
188: Document document = loadDOM(getCfgElement());
189: if (document == null) {
190: // we failed to parse the content file. Intro Parser would have
191: // logged the fact. Parser would also have checked to see if the
192: // content file has the correct root tag.
193: setModelState(true, false, false);
194: return;
195: }
196:
197: // set base for this model.
198: this .base = getBase(getCfgElement());
199:
200: // now load content.
201: loadPages(document, getBundle());
202: loadSharedGroups(document, getBundle());
203:
204: // Attributes of root page decide if we have a static or dynamic case.
205: setModelState(true, true, getHomePage().isDynamic());
206: }
207:
208: /**
209: * Sets the presentation to the given presentation. The model always has the
210: * presentation as the first child, so use that fact. This method is used
211: * for dynamic awarness to enable replacing the new presentation with the
212: * existing one after a model refresh.
213: *
214: * @param presentation
215: */
216: public void setPresentation(IntroPartPresentation presentation) {
217: this .introPartPresentation = presentation;
218: presentation.setParent(this );
219: children.set(0, presentation);
220: }
221:
222: /**
223: * Resolve contributions into this container's children.
224: */
225: protected void resolveChildren() {
226: // now handle config extension.
227: resolveConfigExtensions();
228: resolved = true;
229: }
230:
231: private IConfigurationElement loadPresentation() {
232: // If there is more than one presentation, load first one, and log
233: // rest.
234: IConfigurationElement[] presentationElements = getCfgElement()
235: .getChildren(IntroPartPresentation.TAG_PRESENTATION);
236:
237: IConfigurationElement presentationElement = ModelLoaderUtil
238: .validateSingleContribution(presentationElements,
239: IntroPartPresentation.ATT_HOME_PAGE_ID);
240: return presentationElement;
241: }
242:
243: private void loadConfigurer() {
244: String cname = getCfgElement().getAttribute(ATT_CONFIGURER);
245: if (cname != null) {
246: try {
247: Object obj = getCfgElement().createExecutableExtension(
248: ATT_CONFIGURER);
249: if (obj instanceof IntroConfigurer)
250: configurer = (IntroConfigurer) obj;
251: } catch (CoreException e) {
252: Log.error("Error loading intro configurer", e); //$NON-NLS-1$
253: }
254: }
255: }
256:
257: private void loadTheme() {
258: Preferences pref = IntroPlugin.getDefault()
259: .getPluginPreferences();
260: String pid = Platform.getProduct().getId();
261: String themeId = pref.getString(pid + "_INTRO_THEME"); //$NON-NLS-1$
262: if (themeId.length() == 0)
263: themeId = pref.getString("INTRO_THEME"); //$NON-NLS-1$
264: IConfigurationElement[] elements = Platform
265: .getExtensionRegistry().getConfigurationElementsFor(
266: "org.eclipse.ui.intro.configExtension"); //$NON-NLS-1$
267: IConfigurationElement themeElement = null;
268: for (int i = 0; i < elements.length; i++) {
269: if (elements[i].getName().equals("theme")) { //$NON-NLS-1$
270: String id = elements[i].getAttribute("id"); //$NON-NLS-1$
271: if (themeId != null) {
272: if (id != null && themeId.equals(id)) {
273: // use this one
274: themeElement = elements[i];
275: break;
276: }
277: } else {
278: // see if this one is the default
279: String value = elements[i].getAttribute("default"); //$NON-NLS-1$
280: if (value != null && value.equalsIgnoreCase("true")) { //$NON-NLS-1$
281: themeElement = elements[i];
282: break;
283: }
284: }
285: }
286: }
287: if (themeElement != null) {
288: theme = new IntroTheme(themeElement);
289: }
290: }
291:
292: /**
293: * Loads all pages defined in this config from the xml content file.
294: */
295: private void loadPages(Document dom, Bundle bundle) {
296: String homePageId = getPresentation().getHomePageId();
297: String standbyPageId = getPresentation().getStandbyPageId();
298: Element[] pages = ModelUtil.getElementsByTagName(dom,
299: AbstractIntroPage.TAG_PAGE);
300: for (int i = 0; i < pages.length; i++) {
301: Element pageElement = pages[i];
302: if (pageElement.getAttribute(AbstractIntroIdElement.ATT_ID)
303: .equals(homePageId)) {
304: // Create the model class for the Root Page.
305: homePage = new IntroHomePage(pageElement, bundle, base);
306: homePage.setParent(this );
307: currentPageId = homePage.getId();
308: children.add(homePage);
309: } else if (pageElement.getAttribute(
310: AbstractIntroIdElement.ATT_ID)
311: .equals(standbyPageId)) {
312: // Create the model class for the standby Page.
313: standbyPage = new IntroHomePage(pageElement, bundle,
314: base);
315: standbyPage.setParent(this );
316: // signal that it is a standby page.
317: standbyPage.setStandbyPage(true);
318: children.add(standbyPage);
319: } else {
320: // Create the model class for an intro Page.
321: IntroPage page = new IntroPage(pageElement, bundle,
322: base);
323: page.setParent(this );
324: children.add(page);
325: }
326: }
327: }
328:
329: /**
330: * Loads all shared groups defined in this config, from the DOM.
331: */
332: private void loadSharedGroups(Document dom, Bundle bundle) {
333: Element[] groups = ModelUtil.getElementsByTagName(dom,
334: IntroGroup.TAG_GROUP);
335: for (int i = 0; i < groups.length; i++) {
336: IntroGroup group = new IntroGroup(groups[i], bundle, base);
337: group.setParent(this );
338: children.add(group);
339: }
340: }
341:
342: /**
343: * Handles all the configExtensions to this current model. Resolving
344: * configExts means finding target anchor and inserting extension content at
345: * target. Also, several passes are used to resolve as many extensions as
346: * possible. This allows for resolving nested anchors (ie: anchors to
347: * anchors in contributions).
348: */
349: private void resolveConfigExtensions() {
350: for (int i = 0; i < configExtensionElements.length; i++)
351: resolveConfigExtension(configExtensionElements[i]);
352:
353: // now add all unresolved extensions as model children and log fact.
354: Enumeration keys = unresolvedConfigExt.keys();
355: while (keys.hasMoreElements()) {
356: Element configExtensionElement = (Element) keys
357: .nextElement();
358: IConfigurationElement configExtConfigurationElement = (IConfigurationElement) unresolvedConfigExt
359: .get(configExtensionElement);
360: Bundle bundle = BundleUtil
361: .getBundleFromConfigurationElement(configExtConfigurationElement);
362: String base = getBase(configExtConfigurationElement);
363: children.add(new IntroExtensionContent(
364: configExtensionElement, bundle, base,
365: configExtConfigurationElement));
366:
367: // INTRO: fix log strings.
368: Log
369: .warning("Could not resolve the following configExtension: " //$NON-NLS-1$
370: + ModelLoaderUtil.getLogString(bundle,
371: configExtensionElement,
372: IntroExtensionContent.ATT_PATH));
373: }
374: }
375:
376: private void resolveConfigExtension(
377: IConfigurationElement configExtElement) {
378: // This call will extract the parent folder if needed.
379: Document dom = loadDOM(configExtElement);
380: if (dom == null)
381: // we failed to parse the content file. Intro Parser would
382: // have logged the fact. Parser would also have checked to
383: // see if the content file has the correct root tag.
384: return;
385: resolveConfigExtension(dom, configExtElement);
386: }
387:
388: private void resolveConfigExtension(Document dom,
389: IConfigurationElement configExtElement) {
390:
391: // Find the target of this container extension, and add all its
392: // children to target. Make sure to pass correct bundle and base to
393: // propagate to all children.
394: String base = getBase(configExtElement);
395: Element extensionContentElement = loadExtensionContent(dom,
396: configExtElement, base);
397: if (extensionContentElement == null)
398: // no extension content defined, ignore extension completely.
399: return;
400:
401: if (extensionContentElement.hasAttribute("failed")) { //$NON-NLS-1$
402: // we failed to resolve this configExtension, because target
403: // could not be found or is not an anchor, add the extension to the
404: // list of unresolved configExtensions.
405: // INTRO: an extensionContent is used as a key, instead of the whole
406: // DOM. This is usefull if we need to support multiple extension
407: // contents in one file.
408: if (!unresolvedConfigExt
409: .containsKey(extensionContentElement))
410: unresolvedConfigExt.put(extensionContentElement,
411: configExtElement);
412: return;
413: }
414:
415: // We resolved a contribution. Now load all pages and shared groups
416: // from this config extension. No point adding pages that will never
417: // be referenced. Get the bundle from the extensions since they are
418: // defined in other plugins.
419: Bundle bundle = BundleUtil
420: .getBundleFromConfigurationElement(configExtElement);
421:
422: Element[] pages = ModelUtil.getElementsByTagName(dom,
423: AbstractIntroPage.TAG_PAGE);
424: for (int j = 0; j < pages.length; j++) {
425: // Create the model class for an intro Page.
426: IntroPage page = new IntroPage(pages[j], bundle, base);
427: page.setParent(this );
428: children.add(page);
429: }
430:
431: // load all shared groups from all configExtensions to this model.
432: loadSharedGroups(dom, bundle);
433:
434: // since we resolved a contribution, try resolving some of the
435: // unresolved ones before going on.
436: unresolvedConfigExt.remove(extensionContentElement);
437: tryResolvingExtensions();
438: }
439:
440: private void tryResolvingExtensions() {
441: Enumeration keys = unresolvedConfigExt.keys();
442: while (keys.hasMoreElements()) {
443: Element extensionContentElement = (Element) keys
444: .nextElement();
445: resolveConfigExtension(extensionContentElement
446: .getOwnerDocument(),
447: (IConfigurationElement) unresolvedConfigExt
448: .get(extensionContentElement));
449: }
450: }
451:
452: /**
453: * load the extension content of this configExtension into model classes,
454: * and insert them at target. A config extension can have only ONE extension
455: * content. This is because if the extension fails, we need to be able to
456: * not include the page and group contributions as part of the model. If
457: * extension content has XHTML content (ie: content attribute is defined) we
458: * load extension DOM into target page dom.
459: *
460: * note: the extension Element is returned to enable creating a child model
461: * element on failure.
462: *
463: * @param
464: * @return
465: */
466: private Element loadExtensionContent(Document dom,
467: IConfigurationElement configExtElement, String base) {
468:
469: // get the bundle from the extensions since they are defined in
470: // other plugins.
471: Bundle bundle = BundleUtil
472: .getBundleFromConfigurationElement(configExtElement);
473:
474: Element[] extensionContents = ModelUtil.getElementsByTagName(
475: dom, IntroExtensionContent.TAG_CONTAINER_EXTENSION);
476: if (extensionContents.length == 0) {
477: extensionContents = ModelUtil.getElementsByTagName(dom,
478: IntroExtensionContent.TAG_CONTAINER_REPLACE);
479: }
480:
481: // INTRO: change this. we may need to load more than one extension
482: // content here.
483: // There should only be one container extension. (ver3.0)
484: Element extensionContentElement = ModelLoaderUtil
485: .validateSingleContribution(bundle, extensionContents,
486: IntroExtensionContent.ATT_PATH);
487: if (extensionContentElement == null)
488: // no extensionContent defined.
489: return null;
490: if (UAContentFilter.isFiltered(UAElementFactory
491: .newElement(extensionContentElement),
492: IntroEvaluationContext.getContext())) {
493: // whole extension was filtered
494: return null;
495: }
496:
497: // Create the model class for extension content.
498: IntroExtensionContent extensionContent = new IntroExtensionContent(
499: extensionContentElement, bundle, base, configExtElement);
500: boolean success = false;
501: if (extensionContent.isXHTMLContent())
502: success = loadXHTMLExtensionContent(extensionContent);
503: else
504: success = load3_0ExtensionContent(extensionContent);
505:
506: if (success) {
507: if (extensionContentElement.hasAttribute("failed")) //$NON-NLS-1$
508: extensionContentElement.removeAttribute("failed"); //$NON-NLS-1$
509: } else
510: extensionContentElement.setAttribute("failed", "true"); //$NON-NLS-1$ //$NON-NLS-2$
511:
512: return extensionContentElement;
513: }
514:
515: /**
516: * Insert the extension content into the target.
517: *
518: * @param extensionContent
519: * @return
520: */
521: private boolean loadXHTMLExtensionContent(
522: IntroExtensionContent extensionContent) {
523: String path = extensionContent.getPath();
524: // path must be pageId/anchorID in the case of anchors in XHTML pages.
525: String[] pathSegments = StringUtil.split(path, "/"); //$NON-NLS-1$
526: if (pathSegments.length != 2)
527: // path does not have correct format.
528: return false;
529: AbstractIntroPage targetPage = (AbstractIntroPage) findChild(
530: pathSegments[0], ABSTRACT_PAGE);
531: if (targetPage == null)
532: // target could not be found. Signal failure.
533: return false;
534:
535: // Insert all children of this extension before the target element. Anchors need
536: // to stay in DOM, even after all extensions have been resolved, to enable other
537: // plugins to contribute. Find the target node.
538: Document pageDom = targetPage.getDocument();
539: Element targetElement = targetPage.findDomChild(
540: pathSegments[1], "*"); //$NON-NLS-1$
541: if (targetElement == null)
542: return false;
543:
544: // get extension content
545: Element[] elements = extensionContent.getElements();
546: // insert all children before anchor in page body.
547: for (int i = 0; i < elements.length; i++) {
548: Node targetNode = pageDom.importNode(elements[i], true);
549: // update the src attribute of this node, if defined by w3
550: // specs.
551:
552: ModelUtil.updateResourceAttributes((Element) targetNode,
553: extensionContent);
554: targetElement.getParentNode().insertBefore(targetNode,
555: targetElement);
556: }
557:
558: if (extensionContent.getExtensionType() == IntroExtensionContent.TYPE_REPLACEMENT) {
559: targetElement.getParentNode().removeChild(targetElement);
560: }
561:
562: // now handle style inheritance.
563: // Update the parent page styles. skip style if it is null;
564: String[] styles = extensionContent.getStyles();
565: if (styles != null) {
566: for (int i = 0; i < styles.length; i++)
567: ModelUtil.insertStyle(pageDom, styles[i]);
568: }
569:
570: return true;
571:
572: }
573:
574: /**
575: * Insert the extension content (3.0 format) into the target.
576: *
577: * @param extensionContent
578: * @return
579: */
580: private boolean load3_0ExtensionContent(
581: IntroExtensionContent extensionContent) {
582: String path = extensionContent.getPath();
583: int type = extensionContent.getExtensionType();
584: AbstractIntroElement target = findTarget(this , path,
585: extensionContent.getId());
586: if (target != null
587: && target.isOfType(AbstractIntroElement.ANCHOR) == (type == IntroExtensionContent.TYPE_CONTRIBUTION)) {
588: // insert all children of this extension before the target element/anchor.
589: insertExtensionChildren(target, extensionContent,
590: extensionContent.getBundle(), extensionContent
591: .getBase());
592: // anchors need to stay around to receive other contributions
593: if (type == IntroExtensionContent.TYPE_REPLACEMENT) {
594: AbstractIntroContainer parent = (AbstractIntroContainer) target
595: .getParent();
596: parent.removeChild(target);
597: }
598: handleExtensionStyleInheritence(target, extensionContent);
599: return true;
600: }
601: // appropriate target could not be found. Signal failure.
602: return false;
603: }
604:
605: private void insertExtensionChildren(AbstractIntroElement target,
606: IntroExtensionContent extensionContent, Bundle bundle,
607: String base) {
608: AbstractIntroContainer parent = (AbstractIntroContainer) target
609: .getParent();
610: // insert the elements of the extension before the target
611: String mixinStyle = getMixinStyle(extensionContent);
612: Element[] children = extensionContent.getChildren();
613: parent.insertElementsBefore(children, bundle, base, target,
614: mixinStyle);
615: }
616:
617: private String getMixinStyle(IntroExtensionContent extensionContent) {
618: String path = extensionContent.getPath();
619: if (!path.endsWith("/@")) //$NON-NLS-1$
620: return null;
621: String pageId = path.substring(0, path.length() - 2);
622: IntroModelRoot modelRoot = getModelRoot();
623: if (modelRoot == null)
624: return null;
625: IntroConfigurer configurer = modelRoot.getConfigurer();
626: if (configurer == null)
627: return null;
628: String extensionId = extensionContent.getId();
629: // if this is a replace, take the mixin style as what is being replaced
630: if (extensionContent.getExtensionType() == IntroExtensionContent.TYPE_REPLACEMENT) {
631: IPath ipath = new Path(extensionContent.getPath());
632: String s2 = ipath.segment(1);
633: if (s2 != null && s2.startsWith("@") && s2.length() > 1) { //$NON-NLS-1$
634: extensionId = s2.substring(1);
635: }
636: }
637: return configurer.getMixinStyle(pageId, extensionId);
638: }
639:
640: /**
641: * Updates the inherited styles based on the style attribtes defined in the
642: * configExtension. If we are extending a shared group do nothing. For
643: * inherited alt-styles, we have to cache the bundle from which we inherited
644: * the styles to be able to access resources in that plugin.
645: *
646: * @param include
647: * @param target
648: */
649: private void handleExtensionStyleInheritence(
650: AbstractIntroElement target, IntroExtensionContent extension) {
651:
652: AbstractIntroContainer targetContainer = (AbstractIntroContainer) target
653: .getParent();
654: if (targetContainer.getType() == AbstractIntroElement.GROUP
655: && targetContainer.getParent().getType() == AbstractIntroElement.MODEL_ROOT)
656: // if we are extending a shared group, defined under a config, we
657: // can not include styles.
658: return;
659:
660: // Update the parent page styles. skip style if it is null;
661: String[] styles = extension.getStyles();
662: if (styles != null)
663: targetContainer.getParentPage().addStyles(styles);
664:
665: // for alt-style cache bundle for loading resources.
666: Hashtable altStyles = extension.getAltStyles();
667: if (altStyles != null)
668: targetContainer.getParentPage().addAltStyles(altStyles);
669: }
670:
671: /**
672: * Sets the model state based on all the model classes. Dynamic nature of
673: * the model is always setto false when we fail to load model for any
674: * reason.
675: */
676: private void setModelState(boolean loaded, boolean hasValidConfig,
677: boolean isdynamicIntro) {
678: this .loaded = loaded;
679: this .hasValidConfig = hasValidConfig;
680: this .isdynamicIntro = isdynamicIntro;
681: }
682:
683: /**
684: * Returns true if there is a valid contribution to
685: * org.eclipse.ui.intro.config extension point, with a valid Presentation,
686: * and pages.
687: *
688: * @return Returns the hasValidConfig.
689: */
690: public boolean hasValidConfig() {
691: return hasValidConfig;
692: }
693:
694: /**
695: * @return Returns the introPartPresentation.
696: */
697: public IntroPartPresentation getPresentation() {
698: return introPartPresentation;
699: }
700:
701: public IntroConfigurer getConfigurer() {
702: return configurer;
703: }
704:
705: /**
706: * @return Returns the rootPage.
707: */
708: public IntroHomePage getHomePage() {
709: return homePage;
710: }
711:
712: /**
713: * @return Returns the standby Page. May return null if standby page is not
714: * defined.
715: */
716: public IntroHomePage getStandbyPage() {
717: return standbyPage;
718: }
719:
720: /**
721: * @return all pages *excluding* the Home Page. If all pages are needed,
722: * call <code>(AbstractIntroPage[])
723: * getChildrenOfType(IntroElement.ABSTRACT_PAGE);</code>
724: */
725: public IntroPage[] getPages() {
726: return (IntroPage[]) getChildrenOfType(AbstractIntroElement.PAGE);
727: }
728:
729: /**
730: * @return Returns the isdynamicIntro.
731: */
732: public boolean isDynamic() {
733: return isdynamicIntro;
734: }
735:
736: /**
737: * @return Returns the currentPageId.
738: */
739: public String getCurrentPageId() {
740: return currentPageId;
741: }
742:
743: /**
744: * Sets the current page. If the model does not have a page with the passed
745: * id, the message is logged, and the model retains its old current page.
746: *
747: * @param currentPageId
748: * The currentPageId to set. *
749: * @param fireEvent
750: * flag to indicate if event notification is needed.
751: * @return true if the model has a page with the passed id, false otherwise.
752: * If the method fails, the current page remains the same as the
753: * last state.
754: */
755: public boolean setCurrentPageId(String pageId, boolean fireEvent) {
756: if (pageId.equals(currentPageId))
757: // setting to the same page does nothing. Return true because we did
758: // not actually fail. just a no op.
759: return true;
760:
761: AbstractIntroPage page = (AbstractIntroPage) findChild(pageId,
762: ABSTRACT_PAGE);
763: if (page == null) {
764: // not a page. Test for root page.
765: if (!pageId.equals(homePage.getId())) {
766: // not a page nor the home page.
767: Log
768: .warning("Could not set current page to Intro page with id: " + pageId); //$NON-NLS-1$
769: return false;
770: }
771: }
772:
773: currentPageId = pageId;
774: if (fireEvent)
775: firePropertyChange(CURRENT_PAGE_PROPERTY_ID);
776: return true;
777: }
778:
779: public boolean setCurrentPageId(String pageId) {
780: return setCurrentPageId(pageId, true);
781: }
782:
783: public void addPropertyListener(IPropertyListener l) {
784: propChangeListeners.add(l);
785: }
786:
787: /**
788: * Fires a property changed event. Made public because it can be used to
789: * trigger a UI refresh.
790: *
791: * @param propertyId
792: * the id of the property that changed
793: */
794: public void firePropertyChange(final int propertyId) {
795: Object[] array = propChangeListeners.getListeners();
796: for (int i = 0; i < array.length; i++) {
797: final IPropertyListener l = (IPropertyListener) array[i];
798: SafeRunner.run(new SafeRunnable() {
799:
800: public void run() {
801: l.propertyChanged(this , propertyId);
802: }
803:
804: public void handleException(Throwable e) {
805: super .handleException(e);
806: // If an unexpected exception happens, remove it
807: // to make sure the workbench keeps running.
808: propChangeListeners.remove(l);
809: }
810: });
811: }
812: }
813:
814: public void removePropertyListener(IPropertyListener l) {
815: propChangeListeners.remove(l);
816: }
817:
818: /**
819: * @return Returns the currentPage. return null if page is not found, or if
820: * we are not in a dynamic intro mode.
821: */
822: public AbstractIntroPage getCurrentPage() {
823: if (!isdynamicIntro)
824: return null;
825:
826: AbstractIntroPage page = (AbstractIntroPage) findChild(
827: currentPageId, ABSTRACT_PAGE);
828: if (page != null)
829: return page;
830: // not a page. Test for root page.
831: if (currentPageId.equals(homePage.getId()))
832: return homePage;
833: // return null if page is not found.
834: return null;
835: }
836:
837: /*
838: * (non-Javadoc)
839: *
840: * @see org.eclipse.ui.internal.intro.impl.model.IntroElement#getType()
841: */
842: public int getType() {
843: return AbstractIntroElement.MODEL_ROOT;
844: }
845:
846: /**
847: * Assumes that the passed config element has a "content" attribute. Reads
848: * it and loads a DOM based on that attribute value. It does not explicitly
849: * resolve the resource because this method only loads the introContent and
850: * the configExt content files. ie: in plugin.xml. <br>
851: * This method also sets the base attribute on the root element in the DOM
852: * to enable resolving all resources relative to this DOM.
853: *
854: * @return
855: */
856: protected Document loadDOM(IConfigurationElement cfgElement) {
857: String content = cfgElement.getAttribute(ATT_CONTENT);
858:
859: // To support jarring, extract parent folder of where the intro content
860: // file is. It is expected that all intro content is in that one parent
861: // folder. This works for both content files and configExtension content
862: // files.
863: Bundle domBundle = BundleUtil
864: .getBundleFromConfigurationElement(cfgElement);
865: ModelUtil.ensureFileURLsExist(domBundle, content);
866:
867: // Resolve.
868: content = BundleUtil.getResourceLocation(content, cfgElement);
869: Document document = new IntroContentParser(content)
870: .getDocument();
871:
872: return document;
873: }
874:
875: private String getBase(IConfigurationElement configElement) {
876: String content = configElement.getAttribute(ATT_CONTENT);
877: return ModelUtil.getParentFolderToString(content);
878: }
879:
880: public String resolveVariables(String text) {
881: if (text == null)
882: return null;
883: if (text.indexOf('$') == -1)
884: return text;
885: // resolve
886: boolean inVariable = false;
887: StringBuffer buf = new StringBuffer();
888: int vindex = 0;
889: for (int i = 0; i < text.length(); i++) {
890: char c = text.charAt(i);
891: if (c == '$') {
892: if (!inVariable) {
893: inVariable = true;
894: vindex = i + 1;
895: continue;
896: }
897: inVariable = false;
898: String variable = text.substring(vindex, i);
899: String value = getVariableValue(variable);
900: if (value == null)
901: value = "$" + variable + "$"; //$NON-NLS-1$ //$NON-NLS-2$
902: buf.append(value);
903: continue;
904: } else if (!inVariable)
905: buf.append(c);
906: }
907: return buf.toString();
908: }
909:
910: private String getVariableValue(String variable) {
911: if (variable.equals(VAR_THEME)) {
912: if (theme != null)
913: return theme.getPath();
914: }
915: if (configurer != null)
916: return configurer.getVariable(variable);
917: return null;
918: }
919:
920: public String resolvePath(String extensionId, String path) {
921: if (configurer == null)
922: return null;
923: return configurer.resolvePath(extensionId, path);
924: }
925:
926: public IntroTheme getTheme() {
927: return theme;
928: }
929: }
|