    001    // Copyright 2011-2012 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.internal.yuicompressor;
    016
    017    import org.apache.tapestry5.internal.IOOperation;
    018    import org.apache.tapestry5.internal.TapestryInternalUtils;
    019    import org.apache.tapestry5.internal.services.assets.BytestreamCache;
    020    import org.apache.tapestry5.internal.services.assets.StreamableResourceImpl;
    021    import org.apache.tapestry5.ioc.OperationTracker;
    022    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    023    import org.apache.tapestry5.services.assets.CompressionStatus;
    024    import org.apache.tapestry5.services.assets.ResourceMinimizer;
    025    import org.apache.tapestry5.services.assets.StreamableResource;
    026    import org.slf4j.Logger;
    027
    028    import javax.management.RuntimeErrorException;
    029    import java.io.*;
    030
    031    /**
    032     * Base class for resource minimizers.
    033     *
    034     * @since 5.3
    035     */
    036    public abstract class AbstractMinimizer implements ResourceMinimizer
    037    {
    038        private static final double NANOS_TO_MILLIS = 1.0d / 1000000.0d;
    039
    040        protected final Logger logger;
    041
    042        protected final OperationTracker tracker;
    043
    044        private final String resourceType;
    045
    046        public AbstractMinimizer(Logger logger, OperationTracker tracker, String resourceType)
    047        {
    048            this.logger = logger;
    049            this.tracker = tracker;
    050            this.resourceType = resourceType;
    051        }
    052
    053        public StreamableResource minimize(final StreamableResource input) throws IOException
    054        {
    055            long startNanos = System.nanoTime();
    056
    057            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
    058
    059            final Writer writer = new OutputStreamWriter(bos);
    060
    061            TapestryInternalUtils.performIO(tracker, "Minimizing " + resourceType, new IOOperation()
    062            {
    063                public void perform() throws IOException
    064                {
    065                    try
    066                    {
    067                        doMinimize(input, writer);
    068                    } catch (RuntimeErrorException ex)
    069                    {
    070                        throw new RuntimeException(String.format("Unable to minimize %s: %s", resourceType,
    071                                InternalUtils.toMessage(ex)), ex);
    072                    }
    073
    074                }
    075            });
    076
    077            writer.close();
    078
    079            // The content is minimized, but can still be (GZip) compressed.
    080
    081            StreamableResource output = new StreamableResourceImpl("minimized " + input.getDescription(),
    082                    input.getContentType(), CompressionStatus.COMPRESSABLE,
    083                    input.getLastModified(), new BytestreamCache(bos));
    084
    085            if (logger.isInfoEnabled())
    086            {
    087                long elapsedNanos = System.nanoTime() - startNanos;
    088
    089                int inputSize = input.getSize();
    090                int outputSize = output.getSize();
    091
    092                double elapsedMillis = ((double) elapsedNanos) * NANOS_TO_MILLIS;
    093                // e.g., reducing 100 bytes to 25 would be a (100-25)/100 reduction, or 75%
    094                double reduction = 100d * ((double) (inputSize - outputSize)) / ((double) inputSize);
    095
    096                logger.info(String.format("Minimized %s (%,d input bytes of %s to %,d output bytes in %.2f ms, %.2f%% reduction)",
    097                        input.getDescription(), inputSize, resourceType, outputSize, elapsedMillis, reduction));
    098            }
    099
    100            return output;
    101        }
    102
    103        protected Reader toReader(StreamableResource input) throws IOException
    104        {
    105            InputStream is = input.openStream();
    106
    107            return new InputStreamReader(is, "UTF-8");
    108        }
    109
    110        /**
    111         * Implemented in subclasses to do the actual work.
    112         *
    113         * @param resource
    114         *         content to minimize
    115         * @param output
    116         *         writer for minimized version of input
    117         */
    118        protected abstract void doMinimize(StreamableResource resource, Writer output) throws IOException;
    119    }