All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.webfirmframework.wffweb.css.file.CssFile Maven / Gradle / Ivy

There is a newer version: 12.0.0
Show newest version
/*
 * Copyright 2014-2016 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css.file;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.webfirmframework.wffweb.css.core.CssProperty;

/**
 *
 * @author WFF
 * @since 1.0.0
 */
public abstract class CssFile implements Serializable, Cloneable {

    private static final long serialVersionUID = 1_0_0L;

    private final Map> selectorCssFileBlocks = new LinkedHashMap>();

    private boolean optimizeCssString = true;

    private boolean modified;

    private boolean initialized;

    private boolean rebuild;

    private boolean prependCharset;

    private final Set cssBlocks = new LinkedHashSet() {

        private static final long serialVersionUID = 1_0_0L;

        private final StringBuilder toStringBuilder = new StringBuilder();

        @Override
        public boolean add(final AbstractCssFileBlock e) {
            modified = super.add(e);
            e.addCssFile(CssFile.this);
            addToSelectorCssFileBlocks(e);
            return modified;
        }

        @Override
        public boolean addAll(
                final Collection c) {
            modified = super.addAll(c);
            for (final AbstractCssFileBlock abstractCssFileBlock : c) {
                abstractCssFileBlock.addCssFile(CssFile.this);
                addToSelectorCssFileBlocks(abstractCssFileBlock);
            }
            return modified;
        }

        @Override
        public boolean remove(final Object o) {
            modified = super.remove(o);
            if (o instanceof AbstractCssFileBlock) {
                final AbstractCssFileBlock cssFileBlock = (AbstractCssFileBlock) o;
                cssFileBlock.removeCssFile(CssFile.this);
                removeFromSelectorFileBlocks(cssFileBlock);
            }
            return modified;
        }

        @Override
        public boolean removeAll(final Collection c) {
            setModified(super.removeAll(c));
            for (final Object obj : c) {
                if (obj instanceof AbstractCssFileBlock) {
                    final AbstractCssFileBlock cssFileBlock = (AbstractCssFileBlock) obj;
                    cssFileBlock.removeCssFile(CssFile.this);
                    removeFromSelectorFileBlocks(cssFileBlock);
                }
            }
            return modified;
        }

        @Override
        public void clear() {
            modified = true;
            for (final AbstractCssFileBlock cssFileBlock : this) {
                cssFileBlock.removeCssFile(CssFile.this);
                removeFromSelectorFileBlocks(cssFileBlock);
            }
            super.clear();
        }

        @Override
        public String toString() {
            if (rebuild || modified) {

                toStringBuilder.delete(0, toStringBuilder.length());

                if (optimizeCssString) {
                    for (final Entry> entry : selectorCssFileBlocks
                            .entrySet()) {
                        final Set cssFileBlocks = entry
                                .getValue();
                        if (cssFileBlocks.size() > 0) {

                            boolean exclude = true;

                            final Map cssProperties = new LinkedHashMap();
                            for (final AbstractCssFileBlock cssFileBlock : cssFileBlocks) {

                                // should be called before
                                // cssFileBlock.isExcludeCssBlock()
                                final Map cssPropertiesAsMap = cssFileBlock
                                        .getCssPropertiesAsMap(rebuild);

                                if (!cssFileBlock.isExcludeCssBlock()) {
                                    cssProperties.putAll(cssPropertiesAsMap);
                                    exclude = false;
                                }
                            }

                            if (exclude) {
                                continue;
                            }

                            toStringBuilder.append(entry.getKey());
                            toStringBuilder.append('{');

                            for (final CssProperty cssProperty : cssProperties
                                    .values()) {
                                toStringBuilder
                                        .append(cssProperty.getCssName());
                                toStringBuilder.append(':');
                                toStringBuilder
                                        .append(cssProperty.getCssValue());
                                toStringBuilder.append(';');
                            }
                            toStringBuilder.append('}');
                        }
                    }
                } else {
                    for (final AbstractCssFileBlock cssFileBlock : this) {
                        // this statement should be called before
                        // cssFileBlock.isExcludeCssBlock method
                        final String cssString = cssFileBlock
                                .toCssString(rebuild);
                        if (!cssFileBlock.isExcludeCssBlock()) {
                            toStringBuilder.append(cssString);
                        }
                    }
                }
                setModified(false);
            }

            rebuild = false;
            return toStringBuilder.toString();
        }
    };

    protected final void initCssFile() {
        if (!initialized) {
            updateCssBlocks();
            initialized = true;
        }
    }

    private void updateCssBlocks() {
        cssBlocks.clear();
        try {
            for (final Field field : this.getClass().getDeclaredFields()) {

                if (AbstractCssFileBlock.class.isAssignableFrom(field.getType())
                        && !field.isAnnotationPresent(ExcludeCssBlock.class)) {

                    final boolean accessible = field.isAccessible();
                    field.setAccessible(true);
                    final AbstractCssFileBlock abstractCssFileBlock = (AbstractCssFileBlock) field
                            .get(this);
                    cssBlocks.add(abstractCssFileBlock);
                    field.setAccessible(accessible);
                } else if (CssFile.class.isAssignableFrom(field.getType())
                        && field.isAnnotationPresent(ImportCssFile.class)) {
                    final boolean accessible = field.isAccessible();
                    field.setAccessible(true);
                    final CssFile cssFile = (CssFile) field.get(this);

                    // adding empty set will remove all of the objects.
                    if (cssFile.getCssBlocks().size() > 0) {
                        cssBlocks.addAll(cssFile.getCssBlocks());
                    }
                    field.setAccessible(accessible);
                }
            }
        } catch (final SecurityException e) {
            e.printStackTrace();
        } catch (final IllegalArgumentException e) {
            e.printStackTrace();
        } catch (final IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * @return the cssBlocks
     * @since 1.0.0
     * @author WFF
     */
    public Set getCssBlocks() {
        initCssFile();
        return cssBlocks;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    // it's not a best practice to print css by toString method of this class.
    @Override
    public String toString() {
        return super.toString();
    }

    /**
     * @return the css string. i.e. the contents in the css file as a string.
     * @since 1.0.0
     * @author WFF
     */
    public String toCssString() {
        initCssFile();
        return cssBlocks.toString();
    }

    /**
     * @param rebuild
     *            true to force to rebuild
     * @return the css string. i.e. the contents in the css file as a string.
     * @since 1.0.0
     * @author WFF
     */
    public String toCssString(final boolean rebuild) {
        initCssFile();
        modified = rebuild;
        this.rebuild = rebuild;
        return cssBlocks.toString();
    }

    /**
     * @param os
     *            the {@code OutputStream} object to write
     * @param charset
     *            the charset type of bytes to write
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public void toOutputStream(final OutputStream os, final String charset)
            throws IOException {
        toOutputStream(os, charset, false);
    }

    /**
     * @param os
     *            the {@code OutputStream} object to write
     * @param charsetName
     *            the charset type of bytes to write
     * @param rebuild
     *            the load method in CssBlocked will be invoked a gain
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public void toOutputStream(final OutputStream os, final String charsetName,
            final boolean rebuild) throws IOException {

        if (prependCharset) {
            final String cssCharsetDeclaration = "@CHARSET \"" + charsetName
                    + "\";\n";
            os.write(cssCharsetDeclaration.getBytes(charsetName));
        }

        initCssFile();

        if (isOptimizeCssString()) {
            for (final Entry> entry : selectorCssFileBlocks
                    .entrySet()) {
                final Set cssFileBlocks = entry
                        .getValue();
                if (cssFileBlocks.size() > 0) {

                    boolean exclude = true;

                    final Map cssProperties = new LinkedHashMap();
                    for (final AbstractCssFileBlock cssFileBlock : cssFileBlocks) {

                        // should be called before
                        // cssFileBlock.isExcludeCssBlock method
                        final Map cssPropertiesAsMap = cssFileBlock
                                .getCssPropertiesAsMap(rebuild);

                        if (!cssFileBlock.isExcludeCssBlock()) {
                            cssProperties.putAll(cssPropertiesAsMap);
                            exclude = false;
                        }
                    }

                    if (exclude) {
                        continue;
                    }

                    os.write(entry.getKey().getBytes(charsetName));
                    os.write("{".getBytes(charsetName));

                    for (final CssProperty cssProperty : cssProperties
                            .values()) {
                        os.write(
                                cssProperty.getCssName().getBytes(charsetName));
                        os.write(":".getBytes(charsetName));
                        os.write(cssProperty.getCssValue()
                                .getBytes(charsetName));
                        os.write(";".getBytes(charsetName));
                    }
                    os.write("}".getBytes(charsetName));
                }
            }
        } else {
            for (final AbstractCssFileBlock cssFileBlock : cssBlocks) {
                // this statement should be called before
                // cssFileBlock.isExcludeCssBlock method
                final String cssString = cssFileBlock.toCssString(rebuild);
                if (!cssFileBlock.isExcludeCssBlock()) {
                    os.write(cssString.getBytes(charsetName));
                }
            }
        }
    }

    /**
     * @param os
     *            the {@code OutputStream} object to write
     * @param rebuild
     *            the load method in CssBlocked will be invoked a gain
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public void toOutputStream(final OutputStream os, final boolean rebuild)
            throws IOException {
        toOutputStream(os, Charset.defaultCharset().name(), rebuild);
    }

    /**
     * @param os
     *            the {@code OutputStream} object to write
     * @param charset
     *            the charset type of bytes to write
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public void toOutputStream(final OutputStream os, final Charset charset)
            throws IOException {
        toOutputStream(os, charset.name(), false);
    }

    /**
     * @param os
     *            the {@code OutputStream} object to write
     * @param charset
     *            the charset type of bytes to write
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public void toOutputStream(final OutputStream os, final Charset charset,
            final boolean rebuild) throws IOException {
        toOutputStream(os, charset.name(), rebuild);
    }

    /**
     * @param os
     *            the {@code OutputStream} object to write
     * @throws IOException
     * @since 1.1.2
     * @author WFF
     */
    public void toOutputStream(final OutputStream os) throws IOException {
        toOutputStream(os, Charset.defaultCharset().name(), false);
    }

    /**
     * @param modified
     *            the modified to set
     * @since 1.0.0
     * @author WFF
     */
    void setModified(final boolean modified) {
        this.modified = modified;
    }

    private void addToSelectorCssFileBlocks(
            final AbstractCssFileBlock cssFileBlock) {
        Set abstractCssFileBlocks = selectorCssFileBlocks
                .get(cssFileBlock.getSelectors());
        if (abstractCssFileBlocks == null) {
            abstractCssFileBlocks = new LinkedHashSet();
            selectorCssFileBlocks.put(cssFileBlock.getSelectors(),
                    abstractCssFileBlocks);
        }
        abstractCssFileBlocks.add(cssFileBlock);
    }

    private void removeFromSelectorFileBlocks(
            final AbstractCssFileBlock cssFileBlock) {
        final Set abstractCssFileBlocks = selectorCssFileBlocks
                .get(cssFileBlock.getSelectors());
        if (abstractCssFileBlocks != null) {
            abstractCssFileBlocks.remove(cssFileBlock);
        }
    }

    /**
     * @return true if the toCssString to be optimized.
     * @since 1.0.0
     * @author WFF
     */
    public final boolean isOptimizeCssString() {
        return optimizeCssString;
    }

    /**
     * optimizes the {@code toCssString} output. For instance the optimized
     * output for the following blocks in the {@code CssFile} extended class
     * will be
     * .test4-class{list-style-position:outside;background-repeat:no-repeat;}
     * .
* private CssBlock cssBlock1 = new CssBlock(".test4-class") { *
* @Override
* protected void load(Set cssProperties) { *
* cssProperties.add(ListStylePosition.INSIDE); *
* }
* };
* private CssBlock cssBlock2 = new CssBlock(".test4-class") { *
* @Override
* protected void load(Set cssProperties) { *
* cssProperties.add(BackgroundRepeat.NO_REPEAT); *
* cssProperties.add(ListStylePosition.OUTSIDE); *
* }
* }; * * @param optimizeCssString * the optimizeCssString to set. true to optimize the * {@code toCssString} value and false to turn off optimization. * The default value is true. * @since 1.0.0 * @author WFF */ public void setOptimizeCssString(final boolean optimizeCssString) { this.optimizeCssString = optimizeCssString; } /** * @return the selectorCssFileBlocks * @author WFF * @since 1.0.0 */ final Map> getSelectorCssFileBlocks() { return selectorCssFileBlocks; } /** * @return true or false * @since 1.1.2 * @author WFF */ public boolean isPrependCharset() { return prependCharset; } /** * prepends the charset declaration (eg: @CHARSET "UTF-8";) * while writing css string to the output stream by {@code toOutputStream} * methods. * * @param prependCharset * true to prepend the charset declaration (eg: * @CHARSET "UTF-8";) while writing css string to * the output stream by {@code toOutputStream} methods. * @since 1.1.2 * @author WFF */ public void setPrependCharset(final boolean prependCharset) { this.prependCharset = prependCharset; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy