content: /*
   * Copyright (c) 2008. All rights reserved.
   */
  package ro.isdc.wro.util;
  
  import java.io.BufferedReader;
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.Reader;
  import java.io.StringWriter;
  import java.io.Writer;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.List;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.Future;
  
  import junit.framework.AssertionFailedError;
  
  import org.apache.commons.io.FileUtils;
  import org.apache.commons.io.FilenameUtils;
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.io.filefilter.FalseFileFilter;
  import org.apache.commons.io.filefilter.IOFileFilter;
  import org.apache.commons.io.filefilter.TrueFileFilter;
  import org.apache.commons.io.filefilter.WildcardFileFilter;
  import org.apache.commons.lang3.Validate;
  import org.junit.Assert;
  import org.junit.ComparisonFailure;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.config.Context;
  import ro.isdc.wro.manager.factory.BaseWroManagerFactory;
  import ro.isdc.wro.manager.factory.WroManagerFactory;
  import ro.isdc.wro.model.WroModel;
  import ro.isdc.wro.model.factory.WroModelFactory;
  import ro.isdc.wro.model.group.processor.Injector;
  import ro.isdc.wro.model.group.processor.InjectorBuilder;
  import ro.isdc.wro.model.resource.Resource;
  import ro.isdc.wro.model.resource.ResourceType;
  import ro.isdc.wro.model.resource.locator.UriLocator;
  import ro.isdc.wro.model.resource.locator.factory.AbstractUriLocatorFactory;
  import ro.isdc.wro.model.resource.locator.factory.DefaultUriLocatorFactory;
  import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
  import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
  import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
  import ro.isdc.wro.model.resource.processor.decorator.ProcessorDecorator;
  import ro.isdc.wro.model.resource.processor.factory.SimpleProcessorsFactory;
  
  
  /**
   * WroTestUtils.
   *
   * @author Alex Objelean
   * @created Created on Nov 28, 2008
   */
  public class WroTestUtils {
    private static final Logger LOG = LoggerFactory.getLogger(WroTestUtils.class);
  
    /**
     * @return a {@link BaseWroManagerFactory} which uses an empty model.
     */
    public static BaseWroManagerFactory simpleManagerFactory() {
      return new BaseWroManagerFactory().setModelFactory(simpleModelFactory(new WroModel()));
    }
  
    /**
     * Compare contents of two resources (files) by performing some sort of processing on input resource.
     *
     * @param inputResourceUri
     *          uri of the resource to process.
     * @param expectedContentResourceUri
     *          uri of the resource to compare with processed content.
     * @param processor
     *          a closure used to process somehow the input content.
     */
    public static void compareProcessedResourceContents(final String inputResourceUri,
        final String expectedContentResourceUri, final ResourcePostProcessor processor)
        throws IOException {
      final Reader resultReader = getReaderFromUri(inputResourceUri);
      final Reader expectedReader = getReaderFromUri(expectedContentResourceUri);
      compare(resultReader, expectedReader, processor);
    }
  
    private static Reader getReaderFromUri(final String uri)
        throws IOException {
      // wrap reader with bufferedReader for top efficiency
      return new BufferedReader(new InputStreamReader(createDefaultUriLocatorFactory().locate(uri)));
    }
  
    private static UriLocatorFactory createDefaultUriLocatorFactory() {
      return new DefaultUriLocatorFactory();
    }
  
    public static InputStream getInputStream(final String uri)
        throws IOException {
      return createDefaultUriLocatorFactory().locate(uri);
    }
  
    public static void init(final WroModelFactory factory) {
      final WroManagerFactory managerFactroy = new BaseWroManagerFactory().setModelFactory(factory);
      InjectorBuilder.create(managerFactroy).build().inject(factory);
    }
  
    /**
     * @return the injected processor.
     */
    public static ResourcePreProcessor initProcessor(final ResourcePreProcessor processor) {
      final BaseWroManagerFactory factory = new BaseWroManagerFactory();
      factory.setProcessorsFactory(new SimpleProcessorsFactory().addPreProcessor(processor));
      final Injector injector = InjectorBuilder.create(factory).build();
      injector.inject(processor);
      return processor;
    }
  
    /**
     * @return the injector
     */
    public static void initProcessor(final ResourcePostProcessor processor) {
      initProcessor((ResourcePreProcessor) new ProcessorDecorator(processor));
    }
  
    /**
     * Compare contents of two resources (files) by performing some sort of processing on input resource.
     *
     * @param inputResourceUri
     *          uri of the resource to process.
     * @param expectedContentResourceUri
     *          uri of the resource to compare with processed content.
     * @param processor
     *          a closure used to process somehow the input content.
     */
    public static void compare(final Reader resultReader, final Reader expectedReader,
        final ResourcePostProcessor processor)
        throws IOException {
      final Writer resultWriter = new StringWriter();
      processor.process(resultReader, resultWriter);
      final Writer expectedWriter = new StringWriter();
  
      IOUtils.copy(expectedReader, expectedWriter);
      compare(expectedWriter.toString(), resultWriter.toString());
      expectedReader.close();
      expectedWriter.close();
    }
  
    public static void compare(final InputStream input, final InputStream expected, final ResourcePostProcessor processor)
        throws IOException {
      compare(new InputStreamReader(input), new InputStreamReader(expected), processor);
    }
  
    /**
     * Compare if content of expected stream is the same as content of the actual stream. When compared content is not
     * equal, the assertion error will be thrown.
     *
     * @param expected
     *          {@link InputStream} of the expected content.
     * @param actual
     *          {@link InputStream} of the actual content.
     */
    public static void compare(final InputStream expected, final InputStream actual)
        throws IOException {
      Assert.assertNotNull(expected);
      Assert.assertNotNull(actual);
      final String encoding = Context.get().getConfig().getEncoding();
      compare(IOUtils.toString(expected, encoding), IOUtils.toString(actual, encoding));
      expected.close();
      actual.close();
    }
  
    /**
     * Compares two strings by removing trailing spaces & tabs for correct comparison.
     */
    public static void compare(final String expected, final String actual) {
      try {
        final String in = replaceTabsWithSpaces(expected.trim());
        final String out = replaceTabsWithSpaces(actual.trim());
  
        Assert.assertEquals(in, out);
        LOG.debug("Compare.... [OK]");
      } catch (final ComparisonFailure e) {
        LOG.error("Compare.... [FAIL]", e.getMessage());
        throw e;
      }
    }
  
    /**
     * Replace tabs with spaces.
     *
     * @param input
     *          from where to remove tabs.
     * @return cleaned string.
     */
    private static String replaceTabsWithSpaces(final String input) {
      // replace tabs with spaces
      return input.replaceAll("\\t", "  ").replaceAll("\\r", "");
    }
  
    public static void compareFromSameFolder(final File sourceFolder, final IOFileFilter sourceFileFilter,
        final Transformer<String> toTargetFileName, final ResourcePreProcessor processor) {
      final Collection<File> files = FileUtils.listFiles(sourceFolder, sourceFileFilter, FalseFileFilter.INSTANCE);
      int processedNumber = 0;
      for (final File file : files) {
        LOG.debug("processing: {}", file.getName());
        File targetFile = null;
        try {
          targetFile = new File(sourceFolder, toTargetFileName.transform(file.getName()));
          final InputStream targetFileStream = new FileInputStream(targetFile);
          LOG.debug("comparing with: {}", targetFile.getName());
          try {
            compare(new FileInputStream(file), targetFileStream, new ResourcePostProcessor() {
              public void process(final Reader reader, final Writer writer)
                  throws IOException {
                // ResourceType doesn't matter here
                processor.process(Resource.create("file:" + file.getPath(), ResourceType.CSS), reader, writer);
              }
            });
          } catch (final ComparisonFailure e) {
            LOG.error("Failed comparing: {}", file.getName());
            throw e;
          }
          processedNumber++;
        } catch (final IOException e) {
          LOG.debug("Skip comparison because couldn't find the TARGET file {}. Original cause: {}", targetFile,
              e.getCause());
        } catch (final Exception e) {
          throw WroRuntimeException.wrap(e);
        }
      }
      logSuccess(processedNumber);
    }
  
    private static void logSuccess(final int size) {
      if (size == 0) {
        throw new IllegalStateException("No files compared. Check if there is at least one resource to compare");
      }
      LOG.debug("===============");
      LOG.debug("Successfully processed: {} files.", size);
      LOG.debug("===============");
    }
  
    /**
     * Process and compare all the files from the sourceFolder and compare them with the files from the targetFolder.
     */
    public static void compareFromDifferentFolders(final File sourceFolder, final File targetFolder,
        final ResourcePreProcessor processor)
        throws IOException {
      compareFromDifferentFolders(sourceFolder, targetFolder, TrueFileFilter.TRUE, Transformers.noOpTransformer(),
          processor);
    }
  
    public static void compareFromDifferentFoldersByExtension(final File sourceFolder, final File targetFolder,
        final String extension, final ResourcePreProcessor processor)
        throws IOException {
      compareFromDifferentFolders(sourceFolder, targetFolder, new WildcardFileFilter("*." + extension),
          Transformers.noOpTransformer(), processor);
    }
  
    /**
     * TODO run tests in parallel
     */
    public static void compareFromDifferentFoldersByExtension(final File sourceFolder, final File targetFolder,
        final String extension, final ResourcePostProcessor processor)
        throws IOException {
      compareFromDifferentFolders(sourceFolder, targetFolder, new WildcardFileFilter("*." + extension),
          Transformers.noOpTransformer(), processor);
    }
  
    /**
     * Compares files with the same name from sourceFolder against it's counterpart in targetFolder, but allows source and
     * target files to have different extensions. TODO run tests in parallel
     */
    public static void compareFromDifferentFoldersByName(final File sourceFolder, final File targetFolder,
        final String srcExtension, final String targetExtension, final ResourcePostProcessor processor)
        throws IOException {
      compareFromDifferentFolders(sourceFolder, targetFolder, new WildcardFileFilter("*." + srcExtension),
          Transformers.extensionTransformer("css"), processor);
    }
  
    private static void compareFromDifferentFolders(final File sourceFolder, final File targetFolder,
        final IOFileFilter fileFilter, final Transformer<String> toTargetFileName, final ResourcePostProcessor processor)
        throws IOException {
      // TODO use ProcessorsUtils
      compareFromDifferentFolders(sourceFolder, targetFolder, fileFilter, toTargetFileName, new ResourcePreProcessor() {
        public void process(final Resource resource, final Reader reader, final Writer writer)
            throws IOException {
          processor.process(reader, writer);
        }
      });
    }
  
    /**
     * Applies a function for each file from a folder. The folder should contain at least one file to process, otherwise
     * an exception will be thrown.
     *
     * @param folder
     *          {@link File} representing the folder where the files will be used from processing.
     * @param function
     *          {@link Function} to apply on each found file.
     */
    public static void forEachFileInFolder(final File folder, final Function<File, Void> function) {
      Validate.notNull(function);
      final Collection<File> files = FileUtils.listFiles(folder, TrueFileFilter.TRUE, FalseFileFilter.INSTANCE);
      int processedNumber = 0;
      for (final File file : files) {
        try {
          function.apply(file);
        } catch (final Exception e) {
          throw new RuntimeException("Problem while applying function on file: " + file, e);
        }
        processedNumber++;
      }
      logSuccess(processedNumber);
    }
  
    /**
     * Process and compare the files which a located in different folders.
     *
     * @param sourceFolder
     *          folder where the source files are located.
     * @param targetFolder
     *          folder where the target files are located.
     * @param fileFilter
     *          filter used to select files to process.
     * @param toTargetFileName
     *          {@link Transformer} used to identify the target file name based on source file name.
     * @param preProcessor
     *          {@link ResourcePreProcessor} used to process the source files.
     * @throws IOException
     */
    private static void compareFromDifferentFolders(final File sourceFolder, final File targetFolder,
        final IOFileFilter fileFilter, final Transformer<String> toTargetFileName, final ResourcePreProcessor preProcessor)
        throws IOException {
      LOG.debug("sourceFolder: {}", sourceFolder);
      LOG.debug("targetFolder: {}", targetFolder);
      final Collection<File> files = FileUtils.listFiles(sourceFolder, fileFilter, FalseFileFilter.INSTANCE);
      int processedNumber = 0;
      // TODO use WroUtil#runInParallel for running tests faster
      for (final File file : files) {
        File targetFile = null;
        try {
          targetFile = new File(targetFolder, toTargetFileName.transform(file.getName()));
          final InputStream targetFileStream = new FileInputStream(targetFile);
          LOG.debug("=========== processing: {} ===========", file.getName());
          // ResourceType doesn't matter here
          try {
            compare(new FileInputStream(file), targetFileStream, new ResourcePostProcessor() {
              public void process(final Reader reader, final Writer writer)
                  throws IOException {
                // ResourceType doesn't matter here
                ResourceType resourceType = ResourceType.CSS;
                try {
                  resourceType = ResourceType.get(FilenameUtils.getExtension(file.getPath()));
                } catch (final IllegalArgumentException e) {
                  LOG.warn("unkown resource type for file: {}, assuming resource type is: {}", file.getPath(),
                      resourceType);
                }
                try {
                  preProcessor.process(Resource.create("file:" + file.getPath(), resourceType), reader, writer);
                } catch (final Exception e) {
                  LOG.error("processing failed...", e);
                  throw WroRuntimeException.wrap(e, "Processing failed...");
                }
              }
            });
          } catch (final ComparisonFailure e) {
            LOG.error("failed comparing: {}", file.getName());
            throw e;
          }
          processedNumber++;
        } catch (final IOException e) {
          LOG.debug("Skip comparison because couldn't find the TARGET file {}\n. Original exception: {}", targetFile,
              e.getCause());
        } catch (final Exception e) {
          throw WroRuntimeException.wrap(e, "A problem during transformation occured");
        }
      }
      logSuccess(processedNumber);
    }
  
    /**
     * Runs a task concurrently. Allows to test thread-safe behavior.
     *
     * @param task
     *          a {@link Callable} to run concurrently.
     * @throws Exception
     *           if any of the executed tasks fails.
     */
    public static void runConcurrently(final Callable<Void> task, final int times)
        throws Exception {
      final ExecutorService service = Executors.newFixedThreadPool(5);
      final List<Future<?>> futures = new ArrayList<Future<?>>();
      for (int i = 0; i < times; i++) {
        futures.add(service.submit(task));
      }
      for (final Future<?> future : futures) {
        future.get();
      }
    }
  
    public static void runConcurrently(final Callable<Void>... tasks) throws Exception {
      final ExecutorService service = Executors.newFixedThreadPool(5);
      final List<Future<?>> futures = new ArrayList<Future<?>>();
      for (final Callable<Void> task : tasks) {
        futures.add(service.submit(task));
      }
      for (final Future<?> future : futures) {
        future.get();
      }
    }
  
    /**
     * Run the task concurrently 50 times.
     */
    public static void runConcurrently(final Callable<Void> task)
        throws Exception {
      runConcurrently(task, 50);
    }
  
    /**
     * A blocking operation which waits for the provided function to be true. If the function doesn't return true after a
     * timeout period (expressed in milliseconds), it will throw a {@link RuntimeException}.
     *
     * @param function
     *          a {@link Function} responsible for expressing the until expression. It must return true if the wait
     *          blocking operation should stop.
     * @param timeout
     *          number of milliseconds to wait until expression is true. If this timeout is exceeded, the exception will
     *          be thrown.
     */
    public static void waitUntil(final Function<Void, Boolean> function, final long timeout) {
      final long start = System.currentTimeMillis();
      try {
        while (!function.apply(null)) {
          if (System.currentTimeMillis() - start > timeout) {
            throw new WroRuntimeException("Timeout of " + timeout + "ms exceeded.");
          }
        }
      } catch (final Exception e) {
        throw WroRuntimeException.wrap(e);
      }
    }
  
    /**
     * @return a default {@link Injector} to be used by test classes.
     */
    public static Injector createInjector() {
      return InjectorBuilder.create(new BaseWroManagerFactory()).build();
    }
  
    /**
     * Creates a model factory for a given model.
     */
    public static WroModelFactory simpleModelFactory(final WroModel model) {
      Validate.notNull(model);
      return new WroModelFactory() {
        public WroModel create() {
          return model;
        }
  
        public void destroy() {
        }
      };
    }
  
    /**
     * Asserts that a processor supports provided resource types.
     */
    public static void assertProcessorSupportResourceTypes(final ResourcePreProcessor processor,
        final ResourceType... expectedResourceTypes) {
      final ResourceType[] actualResourceTypes = new ProcessorDecorator(processor).getSupportedResourceTypes();
      try {
        Assert.assertTrue(Arrays.equals(expectedResourceTypes, actualResourceTypes));
      } catch (final AssertionFailedError e) {
        final String message = "actual resourceTypes: " + Arrays.toString(actualResourceTypes) + ", expected are: "
            + Arrays.toString(expectedResourceTypes);
        LOG.error(message);
        Assert.fail(message);
      }
    }
  
    /**
     * @return an implementation of {@link UriLocatorFactory} which always return a valid stream which contains the
     *         resource uri as content.
     */
    public static UriLocatorFactory createResourceMockingLocatorFactory() {
      return new AbstractUriLocatorFactory() {
        public UriLocator getInstance(final String uri) {
          return createResourceMockingLocator();
        }
      };
    }
  
    /**
     * @return an implementation of {@link UriLocator} which always return a valid stream which contains the resource uri
     *         as content.
     */
    public static UriLocator createResourceMockingLocator() {
      return new UriLocator() {
        public InputStream locate(final String uri)
            throws IOException {
          return new ByteArrayInputStream(uri.getBytes());
        }
  
        public boolean accept(final String uri) {
          return true;
        }
      };
    }
  }
  