001package gwt.material.design.client.base;
002
003/*
004 * #%L
005 * GwtMaterial
006 * %%
007 * Copyright (C) 2015 GwtMaterialDesign
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import com.google.gwt.dom.client.Element;
024import com.google.gwt.dom.client.Style;
025import com.google.gwt.dom.client.Style.Float;
026import com.google.gwt.dom.client.Style.FontWeight;
027import com.google.gwt.user.client.ui.*;
028import gwt.material.design.client.base.helper.StyleHelper;
029import gwt.material.design.client.base.mixin.ColorsMixin;
030import gwt.material.design.client.base.mixin.CssNameMixin;
031import gwt.material.design.client.base.mixin.EnabledMixin;
032import gwt.material.design.client.base.mixin.FlexboxMixin;
033import gwt.material.design.client.base.mixin.FocusableMixin;
034import gwt.material.design.client.base.mixin.FontSizeMixin;
035import gwt.material.design.client.base.mixin.GridMixin;
036import gwt.material.design.client.base.mixin.IdMixin;
037import gwt.material.design.client.base.mixin.ScrollspyMixin;
038import gwt.material.design.client.base.mixin.SeparatorMixin;
039import gwt.material.design.client.base.mixin.ShadowMixin;
040import gwt.material.design.client.base.mixin.ToggleStyleMixin;
041import gwt.material.design.client.base.mixin.TooltipMixin;
042import gwt.material.design.client.base.mixin.WavesMixin;
043import gwt.material.design.client.constants.CenterOn;
044import gwt.material.design.client.constants.Display;
045import gwt.material.design.client.constants.Flex;
046import gwt.material.design.client.constants.FlexAlignContent;
047import gwt.material.design.client.constants.FlexAlignItems;
048import gwt.material.design.client.constants.FlexAlignSelf;
049import gwt.material.design.client.constants.FlexDirection;
050import gwt.material.design.client.constants.FlexJustifyContent;
051import gwt.material.design.client.constants.FlexWrap;
052import gwt.material.design.client.constants.HideOn;
053import gwt.material.design.client.constants.Position;
054import gwt.material.design.client.constants.ShowOn;
055import gwt.material.design.client.constants.TextAlign;
056import gwt.material.design.client.constants.WavesType;
057
058import java.util.ArrayList;
059import java.util.HashMap;
060import java.util.List;
061import java.util.Map;
062
063public class MaterialWidget extends ComplexPanel implements HasId, HasEnabled, HasTextAlign, HasColors, HasGrid,
064        HasShadow, Focusable, HasInlineStyle, HasSeparator, HasScrollspy, HasHideOn, HasShowOn, HasCenterOn,
065        HasCircle, HasWaves, HasDataAttributes, HasFloat, HasTooltip, HasFlexbox, HasHoverable, HasFontWeight,
066        HasDepth, HasInitialClasses {
067
068    /**
069     * Configurable features enum see {@link #enableFeature(Feature, boolean)}.
070     */
071    public enum Feature {
072        // Feature for adding or inserting children
073        // before this widget has loaded (attached).
074        ONLOAD_ADD_QUEUE
075    }
076
077    class Appender {
078        Widget widget;
079        int index = -1;
080
081        public Appender(Widget widget, int index) {
082            this.widget = widget;
083            this.index = index;
084        }
085
086        public Appender(Widget widget) {
087            this.widget = widget;
088        }
089    }
090
091    private Map<Feature, Boolean> features;
092    private List<Appender> onLoadAdd;
093
094    private String[] initialClasses;
095
096    private IdMixin<MaterialWidget> idMixin;
097    private EnabledMixin<MaterialWidget> enabledMixin;
098    private CssNameMixin<MaterialWidget, TextAlign> textAlignMixin;
099    private ColorsMixin<MaterialWidget> colorsMixin;
100    private FocusableMixin<MaterialWidget> focusableMixin;
101    private GridMixin<MaterialWidget> gridMixin;
102    private ShadowMixin<MaterialWidget> shadowMixin;
103    private SeparatorMixin<MaterialWidget> separatorMixin;
104    private ScrollspyMixin<MaterialWidget> scrollspyMixin;
105    private CssNameMixin<MaterialWidget, HideOn> hideOnMixin;
106    private CssNameMixin<MaterialWidget, ShowOn> showOnMixin;
107    private CssNameMixin<MaterialWidget, CenterOn> centerOnMixin;
108    private FontSizeMixin<MaterialWidget> fontSizeMixin;
109    private ToggleStyleMixin<MaterialWidget> circleMixin;
110    private WavesMixin<MaterialWidget> wavesMixin;
111    private CssNameMixin<MaterialWidget, Float> floatMixin;
112    private TooltipMixin<MaterialWidget> tooltipMixin;
113    private FlexboxMixin<MaterialWidget> flexboxMixin;
114    private ToggleStyleMixin<MaterialWidget> hoverableMixin;
115    private CssNameMixin<MaterialWidget, FontWeight> fontWeightMixin;
116    private ToggleStyleMixin<MaterialWidget> truncateMixin;
117
118    public MaterialWidget() {
119    }
120
121    public MaterialWidget(Element element) {
122        setElement(element);
123    }
124
125    public MaterialWidget(Element element, String... initialClass) {
126        this(element);
127        setInitialClasses(initialClass);
128    }
129
130    @Override
131    protected void onLoad() {
132        super.onLoad();
133
134        if(initialClasses != null) {
135            for (String intial : initialClasses) {
136                if (!intial.isEmpty()) {
137                    removeStyleName(intial);
138                    addStyleName(intial);
139                }
140            }
141        }
142
143        if(isFeatureEnabled(Feature.ONLOAD_ADD_QUEUE) && onLoadAdd != null) {
144            // Check the on load add items.
145            for (Appender item : onLoadAdd) {
146                if(item.index == -1) {
147                    add(item.widget, (Element) getElement());
148                } else {
149                    insert(item.widget, item.index);
150                }
151            }
152            onLoadAdd.clear();
153        }
154    }
155
156    @Override
157    public void add(Widget child) {
158        super.add(child, (Element)getElement());
159    }
160
161    @Override
162    protected void add(Widget child, com.google.gwt.user.client.Element container) {
163        if(!isAttached() && isFeatureEnabled(Feature.ONLOAD_ADD_QUEUE)) {
164            if(onLoadAdd == null) { onLoadAdd = new ArrayList<>(); }
165            onLoadAdd.add(new Appender(child));
166        } else {
167            super.add(child, container);
168        }
169    }
170
171    @Override
172    protected void insert(Widget child, com.google.gwt.user.client.Element container, int beforeIndex, boolean domInsert) {
173        if(!isAttached() && isFeatureEnabled(Feature.ONLOAD_ADD_QUEUE)) {
174            if(onLoadAdd == null) { onLoadAdd = new ArrayList<>(); }
175            onLoadAdd.add(new Appender(child, beforeIndex));
176        } else {
177            // Regular child addition
178            super.insert(child, container, beforeIndex, domInsert);
179        }
180    }
181
182    /**
183     * Inserts a widget at a specific index
184     *
185     * @param child       - widget to be inserted
186     * @param beforeIndex - index for the widget
187     */
188    public void insert(final Widget child, final int beforeIndex) {
189        insert(child, (Element) getElement(), beforeIndex, true);
190    }
191
192    protected IdMixin<MaterialWidget> getIdMixin() {
193        if(idMixin == null) { idMixin = new IdMixin<>(this); }
194        return idMixin;
195    }
196
197    protected EnabledMixin<MaterialWidget> getEnabledMixin() {
198        if(enabledMixin == null) { enabledMixin = new EnabledMixin<>(this); }
199        return enabledMixin;
200    }
201
202    protected CssNameMixin<MaterialWidget, TextAlign> getTextAlignMixin() {
203        if(textAlignMixin == null) { textAlignMixin = new CssNameMixin<>(this); }
204        return textAlignMixin;
205    }
206
207    protected ColorsMixin<MaterialWidget> getColorsMixin() {
208        if(colorsMixin == null) { colorsMixin = new ColorsMixin<>(this); }
209        return colorsMixin;
210    }
211
212    protected FocusableMixin<MaterialWidget> getFocusableMixin() {
213        if(focusableMixin == null) { focusableMixin = new FocusableMixin<>(this); }
214        return focusableMixin;
215    }
216
217    protected GridMixin<MaterialWidget> getGridMixin() {
218        if(gridMixin == null) { gridMixin = new GridMixin<>(this); }
219        return gridMixin;
220    }
221
222    protected ShadowMixin<MaterialWidget> getShadowMixin() {
223        if(shadowMixin == null) { shadowMixin = new ShadowMixin<>(this); }
224        return shadowMixin;
225    }
226
227    protected SeparatorMixin<MaterialWidget> getSeparatorMixin() {
228        if(separatorMixin == null) { separatorMixin = new SeparatorMixin<>(this); }
229        return separatorMixin;
230    }
231
232    protected ScrollspyMixin<MaterialWidget> getScrollspyMixin() {
233        if(scrollspyMixin == null) { scrollspyMixin = new ScrollspyMixin<>(this); }
234        return scrollspyMixin;
235    }
236
237    protected CssNameMixin<MaterialWidget, HideOn> getHideOnMixin() {
238        if(hideOnMixin == null) { hideOnMixin = new CssNameMixin<>(this); }
239        return hideOnMixin;
240    }
241
242    protected CssNameMixin<MaterialWidget, ShowOn> getShowOnMixin() {
243        if(showOnMixin == null) { showOnMixin = new CssNameMixin<>(this); }
244        return showOnMixin;
245    }
246
247    protected CssNameMixin<MaterialWidget, CenterOn> getCenterOnMixin() {
248        if(centerOnMixin == null) { centerOnMixin = new CssNameMixin<>(this); }
249        return centerOnMixin;
250    }
251
252    protected FontSizeMixin<MaterialWidget> getFontSizeMixin() {
253        if(fontSizeMixin == null) { fontSizeMixin = new FontSizeMixin<>(this); }
254        return fontSizeMixin;
255    }
256
257    protected ToggleStyleMixin<MaterialWidget> getCircleMixin() {
258        if(circleMixin == null) { circleMixin = new ToggleStyleMixin<>(this, "circle"); }
259        return circleMixin;
260    }
261
262    protected ToggleStyleMixin<MaterialWidget> getHoverableMixin() {
263        if(hoverableMixin == null) { hoverableMixin = new ToggleStyleMixin<>(this, "hoverable"); }
264        return hoverableMixin;
265    }
266
267    protected WavesMixin<MaterialWidget> getWavesMixin() {
268        if(wavesMixin == null) { wavesMixin = new WavesMixin<>(this); }
269        return wavesMixin;
270    }
271
272    protected CssNameMixin<MaterialWidget, Float> getFloatMixin() {
273        if(floatMixin == null) { floatMixin = new CssNameMixin<>(this); }
274        return floatMixin;
275    }
276
277    protected TooltipMixin<MaterialWidget> getTooltipMixin() {
278        if(tooltipMixin == null) { tooltipMixin = new TooltipMixin<>(this); }
279        return tooltipMixin;
280    }
281
282    protected FlexboxMixin<MaterialWidget> getFlexboxMixin() {
283        if(flexboxMixin == null) { flexboxMixin = new FlexboxMixin<>(this); }
284        return flexboxMixin;
285    }
286
287    protected CssNameMixin<MaterialWidget, FontWeight> getFontWeightMixin() {
288        if(fontWeightMixin == null) { fontWeightMixin = new CssNameMixin<>(this); }
289        return fontWeightMixin;
290    }
291
292    public ToggleStyleMixin<MaterialWidget> getTruncateMixin() {
293        if(truncateMixin == null) { truncateMixin = new ToggleStyleMixin<>(this, "truncate"); }
294        return truncateMixin;
295    }
296
297    @Override
298    public void setId(String id) {
299        getIdMixin().setId(id);
300    }
301
302    @Override
303    public String getId() {
304        return getIdMixin().getId();
305    }
306
307    @Override
308    public boolean isEnabled() {
309        return getEnabledMixin().isEnabled();
310    }
311
312    @Override
313    public void setEnabled(boolean enabled) {
314        getEnabledMixin().setEnabled(enabled);
315    }
316
317    @Override
318    public TextAlign getTextAlign() {
319        return getTextAlignMixin().getCssName();
320    }
321
322    @Override
323    public void setTextAlign(TextAlign align) {
324        getTextAlignMixin().setCssName(align);
325    }
326
327    @Override
328    public void setBackgroundColor(String bgColor) {
329        getColorsMixin().setBackgroundColor(bgColor);
330    }
331
332    @Override
333    public String getBackgroundColor() {
334        return getColorsMixin().getBackgroundColor();
335    }
336
337    @Override
338    public void setTextColor(String textColor) {
339        getColorsMixin().setTextColor(textColor);
340    }
341
342    @Override
343    public String getTextColor() {
344        return getColorsMixin().getTextColor();
345    }
346
347    @Override
348    public int getTabIndex() {
349        return getFocusableMixin().getTabIndex();
350    }
351
352    @Override
353    public void setAccessKey(char key) {
354        getFocusableMixin().setAccessKey(key);
355    }
356
357    @Override
358    public void setFocus(boolean focused) {
359        getFocusableMixin().setFocus(focused);
360    }
361
362    @Override
363    public void setTabIndex(int index) {
364        getFocusableMixin().setTabIndex(index);
365    }
366
367    @Override
368    public void setGrid(String grid) {
369        getGridMixin().setGrid(grid);
370    }
371
372    @Override
373    public void setOffset(String offset) {
374        getGridMixin().setOffset(offset);
375    }
376
377    @Override
378    public void setShadow(int shadow) {
379        getShadowMixin().setShadow(shadow);
380    }
381
382    @Override
383    public int getShadow() {
384        return getShadowMixin().getShadow();
385    }
386
387    @Override
388    public void setMargin(double margin) {
389        getElement().getStyle().setMargin(margin, Style.Unit.PX);
390    }
391
392    @Override
393    public void setMarginTop(final double margin) {
394        getElement().getStyle().setMarginTop(margin, Style.Unit.PX);
395    }
396
397    @Override
398    public void setMarginLeft(final double margin) {
399        getElement().getStyle().setMarginLeft(margin, Style.Unit.PX);
400    }
401
402    @Override
403    public void setMarginRight(final double margin) {
404        getElement().getStyle().setMarginRight(margin, Style.Unit.PX);
405    }
406
407    @Override
408    public void setMarginBottom(final double margin) {
409        getElement().getStyle().setMarginBottom(margin, Style.Unit.PX);
410    }
411
412    @Override
413    public void setPadding(double padding) {
414        getElement().getStyle().setPadding(padding, Style.Unit.PX);
415    }
416
417    @Override
418    public void setPaddingTop(final double padding) {
419        getElement().getStyle().setPaddingTop(padding, Style.Unit.PX);
420    }
421
422    @Override
423    public void setPaddingLeft(final double padding) {
424        getElement().getStyle().setPaddingLeft(padding, Style.Unit.PX);
425    }
426
427    @Override
428    public void setPaddingRight(final double padding) {
429        getElement().getStyle().setPaddingRight(padding, Style.Unit.PX);
430    }
431
432    @Override
433    public void setPaddingBottom(final double padding) {
434        getElement().getStyle().setPaddingBottom(padding, Style.Unit.PX);
435    }
436
437    @Override
438    public void setGwtDisplay(Style.Display display) {
439        getFlexboxMixin().setGwtDisplay(display);
440    }
441
442    @Override
443    public void setOpacity(double opacity) {
444        getElement().getStyle().setOpacity(opacity);
445    }
446
447    @Override
448    public double getOpacity() {
449        return Double.parseDouble(getElement().getStyle().getOpacity());
450    }
451
452    @Override
453    public void setSeparator(boolean separator) {
454        getSeparatorMixin().setSeparator(separator);
455    }
456
457    @Override
458    public boolean isSeparator() {
459        return getSeparatorMixin().isSeparator();
460    }
461
462    @Override
463    public void setScrollspy(String scrollspy) {
464        getScrollspyMixin().setScrollspy(scrollspy);
465    }
466
467    @Override
468    public String getScrollspy() {
469        return getScrollspyMixin().getScrollspy();
470    }
471
472    @Override
473    public void setCenterOn(CenterOn centerOn) {
474        getCenterOnMixin().setCssName(centerOn);
475    }
476
477    @Override
478    public CenterOn getCenterOn() {
479        return getCenterOnMixin().getCssName();
480    }
481
482    @Override
483    public void setHideOn(HideOn hideOn) {
484        getHideOnMixin().setCssName(hideOn);
485    }
486
487    @Override
488    public HideOn getHideOn() {
489        return getHideOnMixin().getCssName();
490    }
491
492    @Override
493    public void setShowOn(ShowOn showOn) {
494        getShowOnMixin().setCssName(showOn);
495    }
496
497    @Override
498    public ShowOn getShowOn() {
499        return getShowOnMixin().getCssName();
500    }
501
502    @Override
503    public void setFontSize(String fontSize) {
504        getFontSizeMixin().setFontSize(fontSize);
505    }
506
507    @Override
508    public String getFontSize() {
509        return getFontSizeMixin().getFontSize();
510    }
511
512    @Override
513    public void setFontSize(double fontSize, Style.Unit unit) {
514        getFontSizeMixin().setFontSize(fontSize, unit);
515    }
516
517    @Override
518    public void setCircle(boolean circle) {
519        getCircleMixin().setOn(circle);
520    }
521
522    @Override
523    public boolean isCircle() {
524        return getCircleMixin().isOn();
525    }
526
527    @Override
528    public void setWaves(WavesType waves) {
529        getWavesMixin().setWaves(waves);
530    }
531
532    @Override
533    public WavesType getWaves() {
534        return getWavesMixin().getWaves();
535    }
536
537    @Override
538    public void setDataAttribute(String dataAttr, String value) {
539        if(!dataAttr.startsWith("data-")) {
540            dataAttr = "data-" + dataAttr;
541        }
542        getElement().setAttribute(dataAttr, value);
543    }
544
545    @Override
546    public String getDataAttribute(String dataAttr) {
547        if(!dataAttr.startsWith("data-")) {
548            dataAttr = "data-" + dataAttr;
549        }
550        return getElement().getAttribute(dataAttr);
551    }
552
553    @Override
554    public void setFloat(Float floatAlign) {
555        getFloatMixin().setCssName(floatAlign);
556    }
557
558    @Override
559    public Float getFloat() {
560        return StyleHelper.fromStyleName(Float.class, getFloatMixin().getCssName());
561    }
562
563    @Override
564    public String getTooltip() {
565        return getTooltipMixin().getTooltip();
566    }
567
568    @Override
569    public void setTooltip(String tooltip) {
570        getTooltipMixin().setTooltip(tooltip);
571    }
572
573    @Override
574    public Position getTooltipPosition() {
575        return getTooltipMixin().getTooltipPosition();
576    }
577
578    @Override
579    public void setTooltipPosition(Position position) {
580        getTooltipMixin().setTooltipPosition(position);
581    }
582
583    @Override
584    public int getTooltipDelayMs() {
585        return getTooltipMixin().getTooltipDelayMs();
586    }
587
588    @Override
589    public void setTooltipDelayMs(int delayMs) {
590        getTooltipMixin().setTooltipDelayMs(delayMs);
591    }
592
593    public void setVisibility(Style.Visibility visibility) {
594        getElement().getStyle().setVisibility(visibility);
595    }
596
597    @Override
598    public void setDisplay(Display display) {
599        getFlexboxMixin().setDisplay(display);
600    }
601
602    @Override
603    public void setFlexDirection(FlexDirection flexDirection) {
604        getFlexboxMixin().setFlexDirection(flexDirection);
605    }
606
607    @Override
608    public void setFlex(Flex flex) {
609        getFlexboxMixin().setFlex(flex);
610    }
611
612    @Override
613    public void setFlexGrow(Integer flexGrow) {
614        getFlexboxMixin().setFlexGrow(flexGrow);
615    }
616
617    @Override
618    public void setFlexShrink(Integer flexShrink) {
619        getFlexboxMixin().setFlexShrink(flexShrink);
620    }
621
622    @Override
623    public void setFlexBasis(String flexBasis) {
624        getFlexboxMixin().setFlexBasis(flexBasis);
625    }
626
627    @Override
628    public void setFlexOrder(Integer flexOrder) {
629        getFlexboxMixin().setFlexOrder(flexOrder);
630    }
631
632    @Override
633    public void setFlexWrap(FlexWrap flexWrap) {
634        getFlexboxMixin().setFlexWrap(flexWrap);
635    }
636
637    @Override
638    public void setFlexAlignContent(FlexAlignContent flexAlignContent) {
639        getFlexboxMixin().setFlexAlignContent(flexAlignContent);
640    }
641
642    @Override
643    public void setFlexAlignSelf(FlexAlignSelf flexAlignSelf) {
644        getFlexboxMixin().setFlexAlignSelf(flexAlignSelf);
645    }
646
647    @Override
648    public void setFlexAlignItems(FlexAlignItems flexAlignItems) {
649        getFlexboxMixin().setFlexAlignItems(flexAlignItems);
650    }
651
652    @Override
653    public void setFlexJustifyContent(FlexJustifyContent flexJustifyContent) {
654        getFlexboxMixin().setFlexJustifyContent(flexJustifyContent);
655    }
656
657    public void setOverflow(Style.Overflow overflow) {
658        getElement().getStyle().setOverflow(overflow);
659    }
660
661    public void setLayoutPosition(Style.Position position) {
662        getElement().getStyle().setPosition(position);
663    }
664
665    public void setLeft(double value) {
666        getElement().getStyle().setLeft(value, Style.Unit.PX);
667    }
668
669    public void setRight(double value) {
670        getElement().getStyle().setRight(value, Style.Unit.PX);
671    }
672
673    public void setTop(double value) {
674        getElement().getStyle().setTop(value, Style.Unit.PX);
675    }
676
677    public void setBottom(double value) {
678        getElement().getStyle().setBottom(value, Style.Unit.PX);
679    }
680
681    @Override
682    public void setHoverable(boolean hoverable) {
683        getHoverableMixin().setOn(hoverable);
684    }
685
686    @Override
687    public boolean isHoverable() {
688        return getHoverableMixin().isOn();
689    }
690
691    @Override
692    public void setFontWeight(FontWeight fontWeight) {
693        getElement().getStyle().setFontWeight(fontWeight);
694    }
695
696    @Override
697    public String getFontWeight() {
698        return getElement().getStyle().getFontWeight();
699    }
700
701    @Override
702    public void setDepth(int depth) {
703        getElement().getStyle().setZIndex(depth);
704    }
705
706    @Override
707    public int getDepth() {
708        return Integer.parseInt(getElement().getStyle().getZIndex());
709    }
710
711    /** If true the label inside this component will be truncated by ellipsis **/
712    public void setTruncate(boolean truncate) {
713        getTruncateMixin().setOn(truncate);
714    }
715
716    public void stopTouchStartEvent() {
717        stopTouchStartEvent(getElement());
718    }
719
720    // Avoid touch events on mobile devices
721    protected native void stopTouchStartEvent(Element e) /*-{
722        $wnd.jQuery(e).bind('touchstart', function(event) {
723            event.stopPropagation();
724        });
725    }-*/;
726
727    public int getWidth() {
728        return getWidth(getElement());
729    }
730
731    protected native int getWidth(Element element) /*-{
732        return $wnd.jQuery(element).outerWidth();
733    }-*/;
734
735    protected void clearActiveClass(HasWidgets widget) {
736        for(Widget child : widget) {
737            Element element = child.getElement();
738            if(StyleHelper.containsStyle(element.getClassName(), "active")) {
739                element.removeClassName("active");
740            }
741
742            if(child instanceof HasWidgets) {
743                clearActiveClass((HasWidgets)child);
744            }
745        }
746    }
747
748    @Override
749    public void setInitialClasses(String... initialClasses) {
750        this.initialClasses = initialClasses;
751    }
752
753    @Override
754    public String[] getInitialClasses() {
755        return initialClasses;
756    }
757
758    /**
759     * Enable or disable a complex {@link Feature}.<br/>
760     * @param feature the feature to enable.
761     * @param enabled true to enable false to disable.
762     */
763    public void enableFeature(Feature feature, boolean enabled) {
764        if(features == null) { features = new HashMap<>(); }
765        features.put(feature, enabled);
766    }
767
768    /**
769     * Check if a {@link Feature} is enabled.
770     */
771    public boolean isFeatureEnabled(Feature feature) {
772        if(features != null) {
773            Boolean enabled = features.get(feature);
774            return enabled != null && enabled;
775        } else {
776            return false;
777        }
778    }
779}