Package org.auraframework.impl.root.theme

Source Code of org.auraframework.impl.root.theme.ThemeDefImpl$Builder

/*
* Copyright (C) 2013 salesforce.com, inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*         http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.auraframework.impl.root.theme;

import static com.google.common.base.Preconditions.checkState;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.auraframework.builder.ThemeDefBuilder;
import org.auraframework.def.AttributeDef;
import org.auraframework.def.DefDescriptor;
import org.auraframework.def.DefDescriptor.DefType;
import org.auraframework.def.ProviderDef;
import org.auraframework.def.RegisterEventDef;
import org.auraframework.def.RootDefinition;
import org.auraframework.def.ThemeDef;
import org.auraframework.def.ThemeDescriptorProviderDef;
import org.auraframework.def.ThemeMapProviderDef;
import org.auraframework.def.VarDef;
import org.auraframework.expression.PropertyReference;
import org.auraframework.impl.root.RootDefinitionImpl;
import org.auraframework.impl.util.AuraUtil;
import org.auraframework.throwable.quickfix.DefinitionNotFoundException;
import org.auraframework.throwable.quickfix.InvalidDefinitionException;
import org.auraframework.throwable.quickfix.QuickFixException;
import org.auraframework.throwable.quickfix.ThemeValueNotFoundException;
import org.auraframework.util.json.Json;

import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
* Implementation for {@link ThemeDef}.
*/
public final class ThemeDefImpl extends RootDefinitionImpl<ThemeDef> implements ThemeDef {
    private static final long serialVersionUID = -7900230831915100535L;

    private final boolean isCmpTheme;
    private final Map<String, VarDef> vars;
    private final List<DefDescriptor<ThemeDef>> imports;
    private final Set<PropertyReference> expressionRefs;
    private final DefDescriptor<ThemeDef> extendsDescriptor;
    private final DefDescriptor<ThemeDescriptorProviderDef> descriptorProvider;
    private final DefDescriptor<ThemeMapProviderDef> mapProvider;
    private final int hashCode;

    public ThemeDefImpl(Builder builder) {
        super(builder);
        this.isCmpTheme = builder.isCmpTheme;
        this.imports = builder.orderedImmutableImports();
        this.vars = AuraUtil.immutableMap(builder.vars);
        this.extendsDescriptor = builder.extendsDescriptor;
        this.descriptorProvider = builder.descriptorProvider;
        this.mapProvider = builder.mapProvider;
        this.expressionRefs = AuraUtil.immutableSet(builder.expressionRefs);
        this.hashCode = AuraUtil.hashCode(super.hashCode(),
                extendsDescriptor, imports, vars, descriptorProvider, mapProvider);
    }

    @Override
    public boolean isCmpTheme() {
        return isCmpTheme;
    }

    @Override
    public DefDescriptor<ThemeDef> getExtendsDescriptor() {
        return extendsDescriptor;
    }

    @Override
    public DefDescriptor<ThemeDescriptorProviderDef> getDescriptorProvider() {
        return descriptorProvider;
    }

    @Override
    public DefDescriptor<ThemeDef> getConcreteDescriptor() throws QuickFixException {
        if (descriptorProvider == null) {
            return descriptor;
        }

        DefDescriptor<ThemeDef> provided = descriptorProvider.getDef().provide();
        while (provided.getDef().getDescriptorProvider() != null) {
            provided = provided.getDef().getConcreteDescriptor();
        }
        return provided;
    }

    @Override
    public DefDescriptor<ThemeMapProviderDef> getMapProvider() {
        return mapProvider;
    }

    @Override
    public boolean hasVar(String name) throws QuickFixException {
        if (vars.containsKey(name)) {
            return true;
        }
        for (DefDescriptor<ThemeDef> theme : imports) {
            if (theme.getDef().hasVar(name)) {
                return true;
            }
        }
        if (extendsDescriptor != null) {
            if (extendsDescriptor.getDef().hasVar(name)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Optional<Object> getVar(String name) throws QuickFixException {
        Optional<VarDef> def = getVarDef(name);
        return def.isPresent() ? Optional.of(def.get().getValue()) : Optional.absent();
    }

    @Override
    public Optional<VarDef> getVarDef(String name) throws QuickFixException {
        if (vars.containsKey(name)) {
            return Optional.of(vars.get(name));
        }

        for (DefDescriptor<ThemeDef> theme : imports) {
            Optional<VarDef> value = theme.getDef().getVarDef(name);
            if (value.isPresent()) {
                return value;
            }
        }

        if (extendsDescriptor != null) {
            return extendsDescriptor.getDef().getVarDef(name);
        }

        return Optional.absent();
    }

    @Override
    public Map<String, VarDef> getDeclaredVarDefs() {
        return vars;
    }

    @Override
    public List<DefDescriptor<ThemeDef>> getDeclaredImports() {
        return imports;
    }

    @Override
    public Set<String> getDeclaredNames() {
        return vars.keySet();
    }

    @Override
    public Iterable<String> getImportedNames() throws QuickFixException {
        if (imports.isEmpty()) {
            return ImmutableSet.of();
        }

        List<Iterable<String>> iterables = Lists.newArrayList();
        for (DefDescriptor<ThemeDef> theme : imports) {
            iterables.add(theme.getDef().getAllNames());
        }
        return Iterables.concat(iterables);
    }

    @Override
    public Iterable<String> getInheritedNames() throws QuickFixException {
        return extendsDescriptor != null ? extendsDescriptor.getDef().getAllNames() : ImmutableSet.<String>of();
    }

    @Override
    public Iterable<String> getOwnNames() throws QuickFixException {
        return Iterables.concat(getDeclaredNames(), getImportedNames());
    }

    @Override
    public Iterable<String> getAllNames() throws QuickFixException {
        return Iterables.concat(getDeclaredNames(), getImportedNames(), getInheritedNames());
    }

    @Override
    public Set<String> getOverriddenNames() throws QuickFixException {
        return Sets.intersection(ImmutableSet.copyOf(getOwnNames()), ImmutableSet.copyOf(getInheritedNames()));
    }

    @Override
    public void validateDefinition() throws QuickFixException {
        super.validateDefinition();

        for (VarDef def : vars.values()) {
            def.validateDefinition();
        }

        // themes with providers are basically only expected to be used in isolation from other features
        if (descriptorProvider != null || mapProvider != null) {
            if (!vars.isEmpty()) {
                String msg = String.format("Theme %s must not specify vars if using a provider", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            if (!imports.isEmpty()) {
                String msg = String.format("Theme %s must not specify imports if using a provider", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            if (extendsDescriptor != null) {
                String msg = String.format("Theme %s must not use 'extends' and 'provider' attributes together",
                        descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // component-bundle themes can't use a provider
            if (isCmpTheme) {
                String msg = String.format("Component theme %s must not specify a provider", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // namespace default theme should not utilize a provider
            DefDescriptor<ThemeDef> nsDefaultTheme = Themes.getNamespaceDefaultTheme(descriptor);
            if (nsDefaultTheme.equals(descriptor)) {
                String msg = String.format("Namespace-default theme %s must not specify a provider", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }
        }
    }

    @Override
    public void validateReferences() throws QuickFixException {
        super.validateReferences();

        // extends
        if (extendsDescriptor != null) {
            // check if it exists
            if (extendsDescriptor.getDef() == null) {
                throw new DefinitionNotFoundException(extendsDescriptor, getLocation());
            }

            // can't extend itself
            if (extendsDescriptor.equals(descriptor)) {
                String msg = String.format("Theme %s cannot extend itself", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // ensure no circular hierarchy
            DefDescriptor<ThemeDef> current = extendsDescriptor;
            while (current != null) {
                if (current.equals(descriptor)) {
                    String msg = String.format("%s must not through its parent eventually extend itself", descriptor);
                    throw new InvalidDefinitionException(msg, getLocation());
                }
                current = current.getDef().getExtendsDescriptor();
            }

            // it would be a mistake to extend an imported theme
            if (imports.contains(extendsDescriptor)) {
                String msg = String.format("Cannot extend and import from the same theme %s", extendsDescriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // cmp themes can't extend other themes. This is an arbitrary restriction to prevent improper usage.
            // if changing, be sure to look over any impact on appendDependencies as well.
            if (isCmpTheme) {
                String msg = String.format("Component theme %s must not extend any other theme", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // the parent theme must not be a cmp theme. This would usually be a mistake/improper usage.
            if (extendsDescriptor.getDef().isCmpTheme()) {
                String msg = String.format("Theme %s must not extend from a component theme", descriptor);
                throw new InvalidDefinitionException(msg, getLocation());
            }
        }

        // cmp themes cannot import. most of the time this would be improper usage.
        // if changing, be sure to look over any impact on appendDependencies as well.
        if (isCmpTheme && !imports.isEmpty()) {
            throw new InvalidDefinitionException("Component themes cannot import another theme", getLocation());
        }

        for (DefDescriptor<ThemeDef> theme : imports) {
            ThemeDef def = theme.getDef();

            // can't import a cmp theme
            if (def.isCmpTheme()) {
                String msg = String.format("Theme %s cannot be imported because it is a component theme", theme);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // can't import a theme with a parent. This is an arbitrary restriction to enforce a level of var lookup
            // simplicity and prevent misuse of imports.
            if (def.getExtendsDescriptor() != null) {
                String msg = String.format("Theme %s cannot be imported since it uses the 'extends' attribute", theme);
                throw new InvalidDefinitionException(msg, getLocation());
            }

            // can't import a theme that uses a provider.
            if (def.getDescriptorProvider() != null || def.getMapProvider() != null) {
                String msg = String.format("Theme %s cannot be imported since it uses a provider", theme);
                throw new InvalidDefinitionException(msg, getLocation());
            }
        }

        // vars
        for (VarDef def : vars.values()) {
            def.validateReferences();
        }

        // verify var cross references refer to something defined on this theme or on a parent theme. Or if this is a
        // cmp theme it can also refer to something on the namespace default theme.
        Iterable<String> names = getAllNames();

        if (isCmpTheme) {
            DefDescriptor<ThemeDef> nsDefaultTheme = Themes.getNamespaceDefaultTheme(descriptor);
            if (nsDefaultTheme.exists()) {
                names = Iterables.concat(names, nsDefaultTheme.getDef().getAllNames());
            }
        }

        Set<String> namesSet = ImmutableSet.copyOf(names);
        for (PropertyReference ref : expressionRefs) {
            if (!namesSet.contains(ref.toString())) {
                throw new ThemeValueNotFoundException(ref.toString(), descriptor, getLocation());
            }
        }
    }

    @Override
    public void appendDependencies(Set<DefDescriptor<?>> dependencies) {
        super.appendDependencies(dependencies);

        if (descriptorProvider != null) {
            dependencies.add(descriptorProvider);
        }

        if (mapProvider != null) {
            dependencies.add(mapProvider);
        }

        if (extendsDescriptor != null) {
            dependencies.add(extendsDescriptor);
        }

        for (VarDef def : vars.values()) {
            def.appendDependencies(dependencies);
        }

        dependencies.addAll(imports);

        // cmp themes might cross reference a global var from the namespace-default theme
        if (isCmpTheme) {
            Set<String> names = getDeclaredNames();
            DefDescriptor<ThemeDef> nsDefaultTheme = Themes.getNamespaceDefaultTheme(descriptor);
            for (PropertyReference ref : expressionRefs) {
                if (!names.contains(ref.toString())) {
                    dependencies.add(nsDefaultTheme);
                    break;
                }
            }
        }
    }

    @Override
    public void serialize(Json json) throws IOException {
        json.writeMapBegin();
        json.writeMapEntry("imports", imports);
        json.writeMapEntry("vars", vars);
        json.writeMapEnd();
    }

    @Override
    public Map<String, RegisterEventDef> getRegisterEventDefs() throws QuickFixException {
        return null; // events not supported here
    }

    @Override
    public boolean isInstanceOf(DefDescriptor<? extends RootDefinition> other) throws QuickFixException {
        return other.getDefType().equals(DefType.THEME) && descriptor.equals(other);
    }

    @Override
    public List<DefDescriptor<?>> getBundle() {
        return Lists.newArrayList();
    }

    @Override
    public Map<DefDescriptor<AttributeDef>, AttributeDef> getAttributeDefs() throws QuickFixException {
        throw new UnsupportedOperationException("attributes not supported on ThemeDef");
    }

    @Override
    public Map<DefDescriptor<AttributeDef>, AttributeDef> getDeclaredAttributeDefs() {
        throw new UnsupportedOperationException("attributes not supported on ThemeDef");
    }

    @Override
    public AttributeDef getAttributeDef(String name) throws QuickFixException {
        throw new UnsupportedOperationException("attributes not supported on ThemeDef");
    }

    @Override
    public DefDescriptor<ProviderDef> getProviderDescriptor() throws QuickFixException {
        // prevent confusion with theme provider
        throw new UnsupportedOperationException("method not supported on ThemeDef");
    }

    @Override
    public ProviderDef getLocalProviderDef() throws QuickFixException {
        // prevent confusion with theme provider
        throw new UnsupportedOperationException("method not supported on ThemeDef");
    }

    @Override
    public ProviderDef getProviderDef() throws QuickFixException {
        // prevent confusion with theme provider
        throw new UnsupportedOperationException("method not supported on ThemeDef");
    }

    @Override
    public boolean isInConcreteAndHasLocalProvider() throws QuickFixException {
        // prevent confusion with theme provider
        throw new UnsupportedOperationException("method not supported on ThemeDef");
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ThemeDefImpl) {
            ThemeDefImpl other = (ThemeDefImpl) obj;
            return Objects.equal(descriptor, other.descriptor)
                    && Objects.equal(location, other.location)
                    && Objects.equal(extendsDescriptor, other.extendsDescriptor)
                    && Objects.equal(imports, other.imports)
                    && Objects.equal(vars, other.vars)
                    && Objects.equal(descriptorProvider, other.descriptorProvider)
                    && Objects.equal(mapProvider, other.mapProvider);
        }

        return false;
    }

    public static final class Builder extends RootDefinitionImpl.Builder<ThemeDef> implements ThemeDefBuilder {
        private boolean isCmpTheme;
        private DefDescriptor<ThemeDef> extendsDescriptor;
        private DefDescriptor<ThemeDescriptorProviderDef> descriptorProvider;
        private DefDescriptor<ThemeMapProviderDef> mapProvider;
        private Set<PropertyReference> expressionRefs;
        private Set<DefDescriptor<ThemeDef>> imports = Sets.newLinkedHashSet();
        private Map<String, VarDef> vars = Maps.newLinkedHashMap();

        public Builder() {
            super(ThemeDef.class);
        }

        @Override
        public Builder setIsCmpTheme(boolean isCmpTheme) {
            this.isCmpTheme = isCmpTheme;
            return this;
        }

        @Override
        public Builder setExtendsDescriptor(DefDescriptor<ThemeDef> extendsDescriptor) {
            this.extendsDescriptor = extendsDescriptor;
            return this;
        }

        @Override
        public Builder addImport(DefDescriptor<ThemeDef> themeDescriptor) {
            // this check is also done by the handler, but in case this theme is being built by something else we
            // still need to check it. imports must come first in order to correctly indicate that while
            // "last one wins", declared vars will always win out over vars from imports. If that fact changes, this
            // check can go away. This is mainly for simplifying the var lookup implementation, while still
            // matching the most common expected usages of imports vs. declared vars.

            checkState(vars.isEmpty(), "Theme imports must be added before all vars");
            imports.add(themeDescriptor);
            return this;
        }

        @Override
        public Builder addVarDef(VarDef var) {
            vars.put(var.getName(), var);
            return this;
        }

        public Builder setDescriptorProvider(DefDescriptor<ThemeDescriptorProviderDef> descriptorProvider) {
            this.descriptorProvider = descriptorProvider;
            return this;
        }

        public Builder setMapProvider(DefDescriptor<ThemeMapProviderDef> mapProvider) {
            this.mapProvider = mapProvider;
            return this;
        }

        public Map<String, VarDef> vars() {
            return vars;
        }

        public Set<DefDescriptor<ThemeDef>> imports() {
            return imports;
        }

        public Builder addAllExpressionRefs(Collection<PropertyReference> refs) {
            if (expressionRefs == null) {
                expressionRefs = Sets.newHashSet();
            }
            expressionRefs.addAll(refs);
            return this;
        }

        public List<DefDescriptor<ThemeDef>> orderedImmutableImports() {
            return ImmutableList.copyOf(imports).reverse(); // reverse so that lookups follow "last one wins" semantics.
        }

        @Override
        public ThemeDefImpl build() {
            return new ThemeDefImpl(this);
        }

        @Override
        public Map<DefDescriptor<AttributeDef>, AttributeDef> getAttributeDefs() {
            throw new UnsupportedOperationException("use var defs instead of attribute defs");
        }

        @Override
        public void addAttributeDef(DefDescriptor<AttributeDef> attrdesc, AttributeDef attributeDef) {
            throw new UnsupportedOperationException("use var defs instead of attribute defs");
        }

        @Override
        public void addProvider(String name) {
            // prevent confusion with theme provider
            throw new UnsupportedOperationException("use setProviderDescriptor instead");
        }
    }
}
TOP

Related Classes of org.auraframework.impl.root.theme.ThemeDefImpl$Builder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.