blob: 890df68bc09df91d1fc2f2301f8197cb2c352bf8 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.jar.JarEntry;
/**
* A {@link Source} backed by a URL (or optionally by a file).
*/
public abstract class UrlSource implements Source {
private final static String FILE_PROTOCOL = "file";
private final static String JAR_PROTOCOL = "jar";
private final static URI CURRENT_DIR = new File(".").toURI().normalize();
private final static Charset UTF8 = Charset.forName("UTF8");
private final static URI BASE_URI = CURRENT_DIR;
private final URI uri;
private final URI absoluteUri;
private final URI translatedUri;
private final boolean shouldCareAboutLastModified;
private volatile boolean exists = false;
private volatile long lastModified = -1;
private volatile boolean propertiesInitialized = false;
// generally, one or the other of these will be non-null after properties are initialized
private volatile File sourceFile = null;
private volatile JarURLConnection jarConn = null;
protected final PackageLibraryManager packageLibraryManager;
protected UrlSource(URI uri) {
this(uri,null);
}
protected UrlSource(URI uri, PackageLibraryManager slm) {
URI expanded = slm != null ? slm.expandRelativeDartUri(uri) : uri;
if (expanded == null) {
// import("dart:typo") case
expanded = uri;
}
this.uri = BASE_URI.relativize(expanded.normalize());
this.absoluteUri = BASE_URI.resolve(expanded);
this.packageLibraryManager = slm;
if (PackageLibraryManager.isDartUri(this.uri) || PackageLibraryManager.isPackageUri(this.uri)) {
assert slm != null;
this.shouldCareAboutLastModified = false;
this.translatedUri = slm.resolveDartUri(this.absoluteUri);
} else {
this.shouldCareAboutLastModified = true;
this.translatedUri = this.absoluteUri;
}
}
protected UrlSource(File file) {
URI uri = file.toURI().normalize();
if (!file.exists()) {
// TODO(jgw): This is a bit ugly, but some of the test infrastructure depends upon
// non-existant relative files being looked up as classpath resources. This was
// previously embedded in DartSourceFile.getSourceReader().
URL url = getClass().getClassLoader().getResource(file.getPath());
if (url != null) {
uri = URI.create(url.toString());
}
}
this.uri = BASE_URI.relativize(uri);
this.translatedUri = this.absoluteUri = BASE_URI.resolve(uri);
this.packageLibraryManager = null;
this.shouldCareAboutLastModified = true;
}
@Override
public boolean exists() {
initProperties();
return exists;
}
@Override
public long getLastModified() {
initProperties();
return lastModified;
}
@Override
public Reader getSourceReader() throws IOException {
initProperties();
if (sourceFile != null) {
return new InputStreamReader(new FileInputStream(sourceFile), UTF8);
} else if (jarConn != null) {
return new InputStreamReader(jarConn.getInputStream(), UTF8);
}
// fall back case
if (translatedUri != null) {
InputStream stream = translatedUri.toURL().openStream();
if (stream != null) {
return new InputStreamReader(stream, UTF8);
}
}
throw new FileNotFoundException(getName());
}
@Override
public String getUniqueIdentifier() {
return absoluteUri.toString();
}
/**
* Get the translated URI for this source.
*
* @return the translated URI
*/
public URI getTranslatedUri() {
return translatedUri;
}
@Override
public URI getUri() {
return absoluteUri;
}
private void initProperties() {
synchronized (this) {
if (!propertiesInitialized) {
try {
initPropertiesEx();
} catch (Throwable e) {
} finally {
propertiesInitialized = true;
}
}
}
}
/**
* Implementation of {@link #initProperties()} which can throw exceptions.
*/
private void initPropertiesEx() throws Exception {
URI resolvedUri = BASE_URI.resolve(translatedUri);
String scheme = resolvedUri.getScheme();
if (scheme == null || FILE_PROTOCOL.equals(scheme)) {
File file = new File(resolvedUri);
lastModified = file.lastModified();
exists = file.exists();
sourceFile = file;
} else {
URL url = translatedUri.toURL();
if (JAR_PROTOCOL.equals(url.getProtocol())) {
getJarEntryProperties(url);
} else {
/*
* TODO(jbrosenberg): Flesh out the support for other
* protocols, like http, etc. Note, calling
* URLConnection.getLastModified() can be dangerous, some
* URLConnection sub-classes don't have a way to close a
* connection opened by this call. Return 0 for now.
*/
lastModified = 0;
// Default this to true for now.
exists = true;
}
}
}
private void getJarEntryProperties(URL url) {
try {
jarConn = (JarURLConnection) url.openConnection();
// useCaches is usually set to true by default, but make sure here
jarConn.setUseCaches(true);
// See if our entry exists
JarEntry jarEntry = jarConn.getJarEntry();
if (jarEntry != null) {
exists = true;
if (!shouldCareAboutLastModified) {
lastModified = 0;
return;
}
// TODO(jbrosenberg): Note the time field for a jarEntry can be
// unreliable, and is not always required in a jar file. Consider using
// the timestamp on the jar file itself.
lastModified = jarEntry.getTime();
}
if (!exists) {
lastModified = -1;
return;
}
} catch (IOException e) {
exists = false;
lastModified = -1;
}
}
}