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    }