blob: 5874051127d9372977c1f8faac9b7d7cb97849ad [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.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
/**
* A default implementation of {@link DartArtifactProvider} specifying
* generated files be placed in the same directory as source files.
*/
public class DefaultDartArtifactProvider extends DartArtifactProvider {
private final File outputDirectory;
public DefaultDartArtifactProvider() {
this(new File("out"));
}
public DefaultDartArtifactProvider(File outputDirectory) {
this.outputDirectory = outputDirectory;
}
@Override
public Reader getArtifactReader(Source source, String part, String extension)
throws IOException {
if (PackageLibraryManager.isDartUri(source.getUri())) {
DartSource bundledSource = getBundledArtifact(source, source, part, extension);
if (bundledSource != null) {
Reader reader = null;
try {
reader = bundledSource.getSourceReader();
} catch (FileNotFoundException e) {
/* thrown if file doesn't exist, which is fine */
}
if (reader != null) {
return new BufferedReader(reader);
}
}
}
File file = getArtifactFile(source, part, extension);
if (!file.exists()) {
return null;
}
return new BufferedReader(new FileReader(file));
}
@Override
public URI getArtifactUri(Source source, String part, String extension) {
try {
return new URI("file", getArtifactFile(source, part, extension).getPath(), null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public Writer getArtifactWriter(Source source, String part, String extension) throws IOException {
return new BufferedWriter(new FileWriter(makeDirectories(getArtifactFile(source, part,
extension))));
}
@Override
public boolean isOutOfDate(Source source, Source base, String extension) {
if (PackageLibraryManager.isDartUri(base.getUri())) {
Source bundledSource = getBundledArtifact(source, base, "", extension);
if (bundledSource != null && bundledSource.exists()) {
// Note: Artifacts bundled with sources are always up to date
return false;
}
}
File artifactFile = getArtifactFile(base, "", extension);
return !artifactFile.exists() || artifactFile.lastModified() < source.getLastModified();
}
// TODO(jbrosenberg): remove 'source' argument from this method, it's not used
protected DartSource getBundledArtifact(Source source, Source base, String part, String extension) {
LibrarySource library;
URI relativeUri;
if (base instanceof LibrarySource) {
library = (LibrarySource) base;
relativeUri = library.getUri().resolve(".").normalize().relativize(base.getUri());
} else if (base instanceof DartSource){
library = ((DartSource) base).getLibrary();
String name = base.getName();
URI nameUri = URI.create(name).normalize();
relativeUri = library.getUri().resolve(".").normalize().relativize(nameUri);
} else {
throw new AssertionError(base.getClass().getName());
}
DartSource bundledSource;
if (!relativeUri.isAbsolute()) {
bundledSource = library.getSourceFor(fullname(relativeUri.getPath(), part, extension));
} else {
bundledSource = null;
}
return bundledSource;
}
/**
* Answer the artifact file associated with the specified source. Only one
* artifact may be associated with the given extension.
*
* @param source the source file (not <code>null</code>)
* @param part a component of the source file to get a reader for (may be empty).
* @param extension the file extension for this artifact (not
* <code>null</code>, not empty)
* @return the artifact file (not <code>null</code>)
*/
protected File getArtifactFile(Source source, String part, String extension) {
String name = source.getName();
name = URI.create(name).normalize().toString();
name = normalizeArtifactName(name);
File file = new File(outputDirectory, fullname(name, part, extension));
return file;
}
/*
* Removes extraneous punctuation and file path syntax.
*/
private String normalizeArtifactName(String name) {
/**
* For efficiency, String operations are replaced with a single pass over
* the character array data.
*
* Note: This is a refactor of a previous version which used repeated calls
* to String.replace(), which turns out to be unnecessarily expensive. This
* particular method has been identified as being called a large number of
* times, thus the need for the micro-optimization here.
*
* This is the original logic being implemented here:
*
* <code>
* name = name.replace("//", File.separator);
* name = name.replace(":", "");
* name = name.replace("!", "");
* name = name.replace("..", "_");
* </code>
*
* Please update the above if the logic being implemented ever changes.
*
* TODO(jbrosenberg): Figure out a better way such that this normalization
* is no longer needed in the first place. Source objects could be built
* from pre-normalized prefixes, etc. Or if it can be called less often,
* then use pre-compiled Patterns and Matchers instead.
*/
boolean lastCharWasSlash = false;
boolean lastCharWasPeriod = false;
boolean madeChanges = false;
int nameLen = name.length();
char[] newName = new char[nameLen];
int idx = 0;
for (char ch : name.toCharArray()) {
if (lastCharWasPeriod && ch != '.') {
// didn't get a second period, so append the one we did get
newName[idx++] = '.';
lastCharWasPeriod = false;
} else if (lastCharWasSlash && ch != '/') {
// didn't get a second slash, so append the one we did get
newName[idx++] = '/';
lastCharWasSlash = false;
}
switch (ch) {
case ':':
case '!':
// replace ':'s and '!'s with empty string
madeChanges = true;
break;
case '/':
if (lastCharWasSlash) {
// got a second slash, replace with File.separatorChar
madeChanges = true;
newName[idx++] = File.separatorChar;
lastCharWasSlash = false;
} else {
lastCharWasSlash = true;
}
break;
case '.':
if (lastCharWasPeriod) {
// got a second period, replace with a '_'
madeChanges = true;
newName[idx++] = ('_');
lastCharWasPeriod = false;
} else {
lastCharWasPeriod = true;
}
break;
default:
newName[idx++] = ch;
lastCharWasSlash = false;
lastCharWasPeriod = false;
}
}
if (lastCharWasPeriod) {
// didn't get a final second period, so append the one we did get
newName[idx++] = '.';
} else if (lastCharWasSlash) {
// didn't get a final second slash, so append the one we did get
newName[idx++] = '/';
}
if (madeChanges) {
name = new String(newName, 0, idx);
}
return name;
}
private File makeDirectories(File file) {
file.getParentFile().mkdirs();
return file;
}
private String fullname(String name, String part, String extension) {
if (part.isEmpty()) {
return name + "." + extension;
} else {
return name + "$" + part + "." + extension;
}
}
}