/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.dart.engine.source;
import com.google.dart.engine.AnalysisEngine;
import java.io.File;
import java.io.IOException;
import java.net.URI;
/**
* Instances of the class {@code PackageUriResolver} resolve {@code package} URI's in the context of
* an application.
* <p>
* For the purposes of sharing analysis, the path to each package under the "packages" directory
* should be canonicalized, but to preserve relative links within a package, the remainder of the
* path from the package directory to the leaf should not.
*
* @coverage dart.engine.source
*/
public class PackageUriResolver extends UriResolver {
/**
* The package directories that {@code package} URI's are assumed to be relative to.
*/
private File[] packagesDirectories;
/**
* The name of the {@code package} scheme.
*/
public static final String PACKAGE_SCHEME = "package";
/**
* Log exceptions thrown with the message "Required key not available" only once.
*/
private static boolean CanLogRequiredKeyIoException = true;
/**
* Return {@code true} if the given URI is a {@code package} URI.
*
* @param uri the URI being tested
* @return {@code true} if the given URI is a {@code package} URI
*/
public static boolean isPackageUri(URI uri) {
return PACKAGE_SCHEME.equals(uri.getScheme());
}
/**
* Initialize a newly created resolver to resolve {@code package} URI's relative to the given
* package directories.
*
* @param packagesDirectories the package directories that {@code package} URI's are assumed to be
* relative to
*/
public PackageUriResolver(File... packagesDirectories) {
if (packagesDirectories.length < 1) {
throw new IllegalArgumentException("At least one package directory must be provided");
}
this.packagesDirectories = packagesDirectories;
}
@Override
public Source resolveAbsolute(URI uri) {
if (!isPackageUri(uri)) {
return null;
}
String path = uri.getPath();
if (path == null) {
path = uri.getSchemeSpecificPart();
if (path == null) {
return null;
}
}
String pkgName;
String relPath;
int index = path.indexOf('/');
if (index == -1) {
// No slash
pkgName = path;
relPath = "";
} else if (index == 0) {
// Leading slash is invalid
return null;
} else {
// <pkgName>/<relPath>
pkgName = path.substring(0, index);
relPath = path.substring(index + 1);
}
for (File packagesDirectory : packagesDirectories) {
File resolvedFile = new File(packagesDirectory, path);
if (resolvedFile.exists()) {
File canonicalFile = getCanonicalFile(packagesDirectory, pkgName, relPath);
if (isSelfReference(packagesDirectory, canonicalFile)) {
uri = canonicalFile.toURI();
}
return new FileBasedSource(uri, canonicalFile);
}
}
return new FileBasedSource(uri, getCanonicalFile(packagesDirectories[0], pkgName, relPath));
}
@Override
public URI restoreAbsolute(Source source) {
if (source instanceof FileBasedSource) {
String sourcePath = ((FileBasedSource) source).getFile().getPath();
for (File packagesDirectory : packagesDirectories) {
File[] pkgFolders = packagesDirectory.listFiles();
if (pkgFolders != null) {
for (File pkgFolder : pkgFolders) {
try {
String pkgCanonicalPath = pkgFolder.getCanonicalPath();
if (sourcePath.startsWith(pkgCanonicalPath)) {
String relPath = sourcePath.substring(pkgCanonicalPath.length());
return URI.create(PACKAGE_SCHEME + ":" + pkgFolder.getName() + relPath);
}
} catch (Exception e) {
}
}
}
}
}
return null;
}
/**
* Answer the canonical file for the specified package.
*
* @param packagesDirectory the "packages" directory (not {@code null})
* @param pkgName the package name (not {@code null}, not empty)
* @param relPath the path relative to the package directory (not {@code null}, no leading slash,
* but may be empty string)
* @return the file (not {@code null})
*/
protected File getCanonicalFile(File packagesDirectory, String pkgName, String relPath) {
File pkgDir = new File(packagesDirectory, pkgName);
try {
pkgDir = pkgDir.getCanonicalFile();
} catch (IOException e) {
if (!e.getMessage().contains("Required key not available")) {
AnalysisEngine.getInstance().getLogger().logError("Canonical failed: " + pkgDir, e);
} else if (CanLogRequiredKeyIoException) {
CanLogRequiredKeyIoException = false;
AnalysisEngine.getInstance().getLogger().logError("Canonical failed: " + pkgDir, e);
}
}
return new File(pkgDir, relPath.replace('/', File.separatorChar));
}
/**
* @return {@code true} if "file" was found in "packagesDir", and it is part of the "lib" folder
* of the application that contains in this "packagesDir".
*/
private boolean isSelfReference(File packagesDir, File file) {
File rootDir = packagesDir.getParentFile();
if (rootDir == null) {
return false;
}
String rootPath = rootDir.getAbsolutePath();
String filePath = file.getAbsolutePath();
return filePath.startsWith(rootPath + "/lib");
}
}