/* * Copyright 2009 Google Inc. * * 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. */ package com.google.jstestdriver; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.Stack; import org.apache.oro.io.GlobFilenameFilter; import org.apache.oro.text.GlobCompiler; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.name.Named; import com.google.jstestdriver.hooks.FileParsePostProcessor; /** * Handles the resolution of glob paths (*.js) and relative paths. * @author jeremiele@google.com (Jeremie Lenfant-Engelmann) */ public class PathResolver { private final Set<FileParsePostProcessor> processors; private final File basePath; @Inject public PathResolver(@Named("basePath") File basePath, Set<FileParsePostProcessor> processors) { this.basePath = basePath; this.processors = processors; } // TODO(andrewtrenk): This method may not be needed since the File class // can resolve ".." on its own public String resolvePath(String path) { path = FileInfo.formatFileSeparator(path); Stack<String> resolvedPath = new Stack<String>(); String[] tokenizedPath = path.split(FileInfo.SEPARATOR_CHAR); for (String token : tokenizedPath) { if (token.equals("..")) { if (!resolvedPath.isEmpty()) { resolvedPath.pop(); continue; } } resolvedPath.push(token); } return join(resolvedPath); } private String join(Collection<String> collection) { StringBuilder sb = new StringBuilder(); Iterator<String> iterator = collection.iterator(); if (iterator.hasNext()) { sb.append(iterator.next()); while (iterator.hasNext()) { sb.append(FileInfo.SEPARATOR_CHAR); sb.append(iterator.next()); } } return sb.toString(); } private Set<FileInfo> consolidatePatches(Set<FileInfo> resolvedFilesLoad) { Set<FileInfo> consolidated = new LinkedHashSet<FileInfo>(resolvedFilesLoad.size()); FileInfo currentNonPatch = null; for (FileInfo fileInfo : resolvedFilesLoad) { if (fileInfo.isPatch()) { if (currentNonPatch == null) { throw new IllegalStateException("Patch " + fileInfo + " without a core file to patch"); } currentNonPatch.addPatch(fileInfo); } else { consolidated.add(fileInfo); currentNonPatch = fileInfo; } } return consolidated; } /** * Resolves files for a set of FileInfos: * - Expands glob paths (e.g. "*.js") into distinct FileInfos * - Sets last modified timestamp for each FileInfo * * @param unresolvedFiles the FileInfos to resolved * @return the resolved FileInfos */ public Set<FileInfo> resolve(Set<FileInfo> unresolvedFiles) { Set<FileInfo> resolvedFiles = new LinkedHashSet<FileInfo>(); for (FileInfo fileInfo : unresolvedFiles) { String filePath = fileInfo.getFilePath(); if (fileInfo.isWebAddress()) { resolvedFiles.add(new FileInfo(filePath, -1, false, false, null)); } else { File file = basePath != null ? new File(basePath.getAbsoluteFile(), filePath) : new File(filePath); File absoluteDir = file.getParentFile().getAbsoluteFile(); File relativeDir = new File(filePath).getParentFile(); // Get all files for the current FileInfo. This will return one file if the FileInfo // doesn't represent a glob String[] expandedFileNames = expandGlob(filePath, file.getName(), absoluteDir); for (String fileName : expandedFileNames) { String absoluteResolvedFilePath = FileInfo.getPath(absoluteDir, fileName); String relativeResolvedFilePath = FileInfo.getPath(relativeDir, fileName); File resolvedFile = new File(absoluteResolvedFilePath); long timestamp = resolvedFile.lastModified(); FileInfo newFileInfo = new FileInfo(relativeResolvedFilePath, timestamp, fileInfo.isPatch(), fileInfo.isServeOnly(), null); resolvedFiles.add(newFileInfo); } } } resolvedFiles = postProcessFiles(resolvedFiles); return consolidatePatches(resolvedFiles); } private String[] expandGlob(String filePath, String fileNamePattern, File dir) { String[] filteredFiles = dir.list(new GlobFilenameFilter( fileNamePattern, GlobCompiler.DEFAULT_MASK | GlobCompiler.CASE_INSENSITIVE_MASK)); if (filteredFiles == null || filteredFiles.length == 0) { try { String error = "The patterns/paths " + filePath + " (" + dir + ") " + " used in the configuration" + " file didn't match any file, the files patterns/paths need to" + " be relative " + basePath.getCanonicalPath(); throw new IllegalArgumentException(error); } catch (IOException e) { throw new RuntimeException(e); } } Arrays.sort(filteredFiles, String.CASE_INSENSITIVE_ORDER); return filteredFiles; } public List<Plugin> resolve(List<Plugin> plugins) { List<Plugin> resolved = Lists.newLinkedList(); for (Plugin plugin : plugins) { resolved.add(plugin.getPluginFromPath(resolvePath(plugin.getPathToJar()))); } return resolved; } private Set<FileInfo> postProcessFiles(Set<FileInfo> resolvedFiles) { Set<FileInfo> processedFiles = resolvedFiles; for (FileParsePostProcessor processor : processors) { processedFiles = processor.process(resolvedFiles); } return processedFiles; } }