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
015package org.apache.tapestry5.internal.yuicompressor;
016
017import org.apache.tapestry5.internal.IOOperation;
018import org.apache.tapestry5.internal.TapestryInternalUtils;
019import org.apache.tapestry5.internal.services.assets.BytestreamCache;
020import org.apache.tapestry5.internal.services.assets.StreamableResourceImpl;
021import org.apache.tapestry5.ioc.OperationTracker;
022import org.apache.tapestry5.ioc.internal.util.InternalUtils;
023import org.apache.tapestry5.services.assets.CompressionStatus;
024import org.apache.tapestry5.services.assets.ResourceMinimizer;
025import org.apache.tapestry5.services.assets.StreamableResource;
026import org.slf4j.Logger;
027
028import javax.management.RuntimeErrorException;
029import java.io.*;
030
031/**
032 * Base class for resource minimizers.
033 *
034 * @since 5.3
035 */
036public 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}