com.google.gxp.compiler.fs..svn.text-base.FileRef.svn-base Maven / Gradle / Ivy
Show all versions of google-gxp Show documentation
/*
* Copyright (C) 2008 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.gxp.compiler.fs;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import java.io.*;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.Adler32;
import javax.tools.FileObject;
import javax.tools.JavaFileObject.Kind;
/**
* A value object that refers to a file in some {@code FileSystem}s.
*
* A {@code FileRef}'s "name" consists of a leading slash ("/") followed by
* a sequence of zero or more slash separated components. Only the canonical
* name "/" may end in a slash. Adjacent slashes are collapsed into a single
* slash. Sample legal names are: "/" and "/a/b", but not "/a/" or "/a//b".
* (The latter two names will be normalized into "/a" and "/a/b".) This
* abstracts away from the system's notion of what a filename looks like,
* whether it's "C:\windows\foo.txt" or "/usr/include/linux/stddef.h".
*
*
FileRef doesn't have any support for special filenames like "." or "..",
* but some implementations of {@link FileSystem#parseFilename(String)}
* will resolve these when parsing.
*
*
Note that there is no such thing as a relative {@code FileRef}; {@code
* FileRef}s are always absolute. The {@link FileRef#join(String)} method may
* be used to create {@code FileRef}s relative to existing {@code FileRef}s.
*
*
You can convert between the types of filenames the user is used to
* seeing, "filenames", and {@code FileRef} objects using {@link
* FileSystem#parseFilename(String)} and {@link
* FileRef#toFilename()}.
*/
public final class FileRef implements FileObject {
private final FileStore store;
private final String name;
// regex that matches 0 or more legal extension characters
private static final String EXTENSION_CHARS = "[^\\./]*";
private static final Pattern SUFFIX_CHARS_PATTERN =
Pattern.compile("^[^\\/]*");
private static final Pattern EXTENSION_PATTERN =
Pattern.compile("\\.(" + EXTENSION_CHARS + ")$");
public FileRef(FileStore store, String name) {
this.store = Preconditions.checkNotNull(store);
this.name = normalize(Preconditions.checkNotNull(name));
}
public String getName() {
return name;
}
public URI toUri() {
return store.toUri(this);
}
/**
* Returns the time that the file was last modified, measured in milliseconds
* since the epoch.
*/
public long getLastModified() {
return store.getLastModified(this);
}
/**
* @return a {@code Adler32} checksum of the file contents or 0 if the file
* cannot be read.
*/
public long getChecksum() {
try {
Adler32 checksum = new Adler32();
InputStream is = openInputStream();
checksum.update(ByteStreams.toByteArray(is));
is.close();
return checksum.getValue();
} catch (IOException e) {
return 0;
}
}
/**
* Delete this file
*/
public boolean delete() {
return store.delete(this);
}
/**
* Opens this filename for writing text.
*
*
The {@code Writer} returned will throw a {@link
* java.nio.charset.UnmappableCharacterException} if unmappable characters
* (ie: characters that do not exist in the specified encoding) are written
* to it.
*/
public Writer openWriter(Charset encoding) throws IOException {
return new OutputStreamWriter(openOutputStream(), encoding.newEncoder());
}
public Writer openWriter() throws IOException {
return openWriter(store.getDefaultCharset());
}
/**
* Opens this filename for reading text.
*
* TODO(harryh): the below note makes this not properly implement FileObject
* but I'm intentionally leaving it this way for now, because I think this
* is better. Might have to revisit this decision later.
*
*
The {@code Writer} returned will throw a {@link
* java.nio.charset.UnmappableCharacterException} if unmappable characters
* (ie: characters that do not exist in the specified encoding) are written
* to it.
*/
public Reader openReader(Charset encoding) throws IOException {
return new InputStreamReader(openInputStream(), encoding.newDecoder());
}
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
return openReader(store.getDefaultCharset());
}
/**
* Opens this filename for reading bytes.
*/
public InputStream openInputStream() throws IOException {
return store.openInputStream(this);
}
/**
* Opens this filename for writing bytes.
*/
public OutputStream openOutputStream() throws IOException {
return store.openOutputStream(this);
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return CharStreams.toString(openReader(ignoreEncodingErrors));
}
/**
* @return a new FileRef where the final extension has been removed.
* If the filename has no suffx, return this.
*/
public FileRef removeExtension() {
Matcher m = EXTENSION_PATTERN.matcher(name);
return m.find()
? new FileRef(this.store, name.substring(0, m.start()))
: this;
}
public Kind getKind() {
Matcher m = EXTENSION_PATTERN.matcher(name);
if (m.find()) {
String extension = name.substring(m.start());
for (Kind kind : Kind.values()) {
if (extension.equals(kind.extension)) {
return kind;
}
}
}
return Kind.OTHER;
}
/**
* @return a new FileRef with an additional suffix. Note that the suffix
* includes the dot.
*/
public FileRef addSuffix(String suffix) {
if (!SUFFIX_CHARS_PATTERN.matcher(suffix).matches()) {
throw new IllegalArgumentException("Illegal characters in suffix,"
+ " \"" + suffix + "\".");
}
return new FileRef(this.store, name + suffix);
}
public String toFilename() {
return store.toFilename(this);
}
public String toRelativeFilename() {
return store.toRelativeFilename(this);
}
/**
* Joins a path suffix onto this FileRef creating a new FileRef which is
* effectively 'suffix' relative to 'this'.
* eg: new FileRef("/a/b").join("/x/y") is new FileRef("/a/b/x/y")
*/
public FileRef join(String suffix) {
return new FileRef(this.store, this.name + "/" + suffix);
}
/**
* @return whether this filename is an ancestor of the specified FileRef,
* "that".
*/
public boolean isAncestorOf(FileRef that) {
return store.equals(that.store)
&& ("/".equals(name) || that.name.startsWith(name + "/"));
}
/**
* @return a String representation of this FileRef for debugging purposes.
*/
@Override
public String toString() {
return getClass().getSimpleName()
+ "(\"" + store + ":" + name + "\")";
}
@Override
public int hashCode() {
return Objects.hashCode(store, name);
}
@Override
public boolean equals(Object that) {
return this == that ||
(that instanceof FileRef && this.equals((FileRef) that));
}
public boolean equals(FileRef that) {
return this.store.equals(that.store) && this.name.equals(that.name);
}
private static final Pattern NORMAL_PATTERN =
Pattern.compile("/([^/]+(/[^/]+)*)?");
private static final Pattern MULTIPLE_SLASH_PATTERN =
Pattern.compile("//+");
private static String normalize(String name) {
if (NORMAL_PATTERN.matcher(name).matches()) {
// short-circuit if string already appears to be normalized
return name;
}
String newName = MULTIPLE_SLASH_PATTERN.matcher("/" + name).replaceAll("/");
if ((newName.length() > 1) && newName.endsWith("/")) {
return newName.substring(0, newName.length() - 1);
}
return newName;
}
}