package net.ayld.facade.model; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableSet; import com.google.common.io.Resources; import net.ayld.facade.dependency.resolver.DependencyResolver; import net.ayld.facade.util.Components; import net.ayld.facade.util.Tokenizer; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.Set; /** * Represents a Java source file. * * Upon creation this class parses the Java source it is being created from and validates it's correctness. * Throws {@link IllegalArgumentException} if the source file fails validation. * */ public class SourceFile { public static final String EXTENSION = "java"; public static final String PACKAGE_KEYWORD = "package"; public static final String IMPORT_KEYWORD = "import"; public static final String WILDCARD_IMPORT_SUFFIX = "*"; public static final String CLASS_KEYWORD = "class"; public static final String PUBLIC_KEYWORD = "public"; private static Set<String> VALID_SOURCE_FILE_FIRST_WORDS = ImmutableSet.of(IMPORT_KEYWORD, PACKAGE_KEYWORD); private Set<ClassName> dependencies; private final File source; private SourceFile(File sourceFile) { try { if (!isSourceFile(sourceFile)) { throw new IllegalArgumentException(sourceFile + " does not look like Java source"); } } catch (IOException e) { throw new IllegalArgumentException(e); } this.source = sourceFile; } /** * Creates a {@link SourceFile} from the given absolute file path. * The path should point to a valid .java source file. * The given file will be validated on creation and a {@link IllegalArgumentException} will be thrown if the validation fails. * * @param absPath absolute path to a .java source file * * @return a {@link SourceFile} * */ public static SourceFile fromFilepath(String absPath) { return new SourceFile(new File(absPath)); } /** * Creates a {@link SourceFile} from the given relative path. The given path should be part of your classpath * and point to a valid .java source file. The given file will be validated on creation and * a {@link IllegalArgumentException} will be thrown if the validation fails. * * @param path relative path to a .java source file on the classpath * * @return a {@link SourceFile} * */ public static SourceFile fromClasspath(String path) { try { return new SourceFile(new File(Resources.getResource(path).toURI())); } catch (URISyntaxException e) { throw new IllegalArgumentException("can not find a file at: " + path); } } /** * Creates a {@link SourceFile} from the given {@link File}. * The given file should be a valid .java source file, otherwise an {@link IllegalArgumentException} will be thrown. * * @param file a .java source file * * @return a {@link SourceFile} * */ public static SourceFile fromFile(File file) { if (file == null) { throw new IllegalArgumentException("can't create a SourceFile from a null file"); } return fromFilepath(file.getAbsolutePath()); } /** * Returns the dependencies of this {@link SourceFile} * */ public Set<ClassName> dependencies() { final DependencyResolver<SourceFile> sourceDependencyResolver = Components.SOURCE_DEPENDENCY_RESOLVER.getInstance(); if (dependencies == null) { try { dependencies = sourceDependencyResolver.resolve(this); } catch (IOException e) { throw new IllegalStateException(e); } } return ImmutableSet.copyOf(dependencies); } public File physicalFile() { return new File(source.getAbsolutePath()); } private static boolean isSourceFile(File sourceFile) throws IOException { // TODO moar checks needed this is not enough if (sourceFile == null) { return false; } final String name = sourceFile.getName(); final String extension = Tokenizer.delimiter(".").tokenize(name).lastToken(); if (!extension.equals(EXTENSION)) { return false; } final String sourceFileContent = Resources.toString(sourceFile.toURI().toURL(), Charsets.UTF_8); final String firstLine = Tokenizer.delimiter("\n").tokenize(sourceFileContent).firstToken(); final String firstWord = Tokenizer.delimiter(" ").tokenize(firstLine).firstToken(); return VALID_SOURCE_FILE_FIRST_WORDS.contains(firstWord); } @Override public int hashCode() { return source.hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof SourceFile)) { return false; } final SourceFile other = (SourceFile) obj; return other.physicalFile().equals(this.source); } }