/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 flex2.compiler; import flex2.compiler.common.SinglePathResolver; import flex2.compiler.io.FileUtil; import flex2.compiler.io.LocalFile; import flex2.compiler.io.VirtualFile; import flex2.compiler.util.CompilerMessage; import java.io.File; import java.util.*; /** * A list of paths specified by the -source-path option, where * dependencies, following the single public definition rule, can be * resolved. * * @author Clement Wong */ public class SourcePath extends SourcePathBase implements SinglePathResolver { protected final List<File> directories; //private ApplicationCache applicationCache; public SourcePath(VirtualFile[] classPath, VirtualFile appPath, String[] mimeTypes, boolean allowSourcePathOverlap) { this(mimeTypes, allowSourcePathOverlap); addApplicationParentToSourcePath(appPath, classPath, this.directories); addPathElements(classPath, this.directories, allowSourcePathOverlap, warnings); } public SourcePath(String[] mimeTypes, boolean allowSourcePathOverlap) { super(mimeTypes, allowSourcePathOverlap); directories = new LinkedList<File>(); } public void addPathElements(VirtualFile[] classPath) { addPathElements(classPath, directories, allowSourcePathOverlap, warnings); } /* private Source newSource(File file, File pathRoot, String namespaceURI, String localPart) { Source source = new Source(new LocalFile(file), new LocalFile(pathRoot), namespaceURI.replace('.', '/'), localPart, this, false, false); if (applicationCache != null) { String className = CompilerAPI.constructClassName(namespaceURI, localPart); Source cachedSource = applicationCache.getSource(className); if ((cachedSource != null) && !cachedSource.isUpdated()) { CompilationUnit cachedCompilationUnit = cachedSource.getCompilationUnit(); if (cachedSource.getPathRoot().equals(source.getPathRoot()) && (cachedCompilationUnit != null) && cachedCompilationUnit.hasTypeInfo) { CompilationUnit compilationUnit = source.newCompilationUnit(cachedCompilationUnit.getSyntaxTree(), new CompilerContext()); Source.copyCompilationUnit(cachedCompilationUnit, compilationUnit, true); source.setFileTime(cachedSource.getFileTime()); cachedSource.reused(); // We somehow need to validate that other reused // sources, which depend on this source, reference // the same slots. Or maybe it's good enough just // to validate that the slots referenced by this // source are the same as the slots referenced by // the dependencies. Something to ponder. } } } return source; } // see if the Source object continues to be the first choice given a QName. boolean checkPreference(Source s) { assert s.getOwner() == this; String relativePath = constructRelativePath(s), pathRoot = s.getPathRoot().getName(); if (relativePath == null) { // not possible, but don't disrupt the flow... return true; } boolean thisPath = false; for (int i = 0, size = directories.size(); i < size; i++) { File d = directories.get(i), f = null; if (pathRoot.equals(FileUtil.getCanonicalPath(d))) { thisPath = true; } try { f = findFile(d, relativePath, mimeTypes); } catch (CompilerException ex) { removeSource(s); return false; } if (f != null && !thisPath) { removeSource(s); return false; } } return true; } protected Source findFile(String className, String namespaceURI, String localPart) throws CompilerException { String p = className.replace(':', '.').replace('.', File.separatorChar); Source s = null; for (int i = 0, size = directories.size(); i < size; i++) { File f, d = directories.get(i); if ((f = findFile(d, p, mimeTypes)) != null) { sources.put(className, s = newSource(f, d, namespaceURI, localPart)); return s; } } return null; } public boolean hasPackage(String packageName) { for (int i = 0, size = directories.size(); i < size; i++) { File d = directories.get(i); if (hasDirectory(d, packageName)) { return true; } } return false; } public boolean hasDefinition(QName qName) { String className = CompilerAPI.constructClassName(qName.getNamespace(), qName.getLocalPart()); if (misses.contains(className)) { return false; } if (hits.contains(className)) { return true; } String p = className.replace(':', '.').replace('.', File.separatorChar); for (int i = 0, size = directories.size(); i < size; i++) { File f, d = directories.get(i); try { if ((f = findFile(d, p, mimeTypes)) != null) { hits.add(className); return true; } } catch (CompilerException ex) { } } misses.add(className); return false; } private boolean hasDirectory(File dir, String packageName) { if (packageName.length() == 0) { return true; } String relativePath = packageName.replace('.', File.separatorChar); String fullPath = dir.getPath() + File.separator + relativePath; if (dirs.get(fullPath) == NO_DIR) { return false; } boolean result = new File(dir, relativePath).isDirectory(); dirs.put(fullPath, result ? fullPath : NO_DIR); return result; } public List<File> getPaths() { return directories; } */ /** * Resolves paths with a leading slash and relative to a Source * Path directory. */ public VirtualFile resolve(String path) { if (path.charAt(0) == '/') { String relativePath = path.substring(1); for (File directory : directories) { File file = FileUtil.openFile(directory, relativePath); if ((file != null) && file.exists()) { return new LocalFile(file); } } } return null; } /* public void setApplicationCache(ApplicationCache applicationCache) { this.applicationCache = applicationCache; } */ } /** * @author Clement Wong */ abstract class SourcePathBase { protected final static String NO_DIR = ""; static void addApplicationParentToSourcePath(VirtualFile appPath, VirtualFile[] classPath, List<File> directories) { if (appPath != null) { File f = FileUtil.openFile(appPath.getParent()); // if (f != null && f.isDirectory()) if (f != null && f.isDirectory() && (FileUtil.isSubdirectoryOf(appPath.getParent(), classPath) == -1)) { directories.add(f); } } } static void addPathElements(VirtualFile[] classPath, List<File> directories, boolean allowSourcePathOverlap, List<ClasspathOverlap> warnings) { boolean badPaths = false; for (int i = 0, length = (classPath == null) ? 0 : classPath.length; i < length; i++) { String path = classPath[i].getName(); File f = FileUtil.openFile(path); if (f != null && f.isDirectory()) { if (!allowSourcePathOverlap && !badPaths) { int index = FileUtil.isSubdirectoryOf(f, directories); if (index != -1) { String dirPath = directories.get(index).getAbsolutePath(); if (checkValidPackageName(path, dirPath)) { // C: don't want to use ThreadLocalToolkit here... // preilly: don't use logError below, because we don't stop // compiling and if the error count is non-zero downstream mayhem // occurs. For example, no SWC's get loaded, which makes it // alittle tough to compile. warnings.add(new ClasspathOverlap(path, dirPath)); badPaths = true; } } } directories.add(f); } } } private static boolean checkValidPackageName(String path1, String path2) { if (path1.equals(path2)) return true; String packagePath = path1.length() > path2.length() ? path1.substring(path2.length()) : path2.substring(path1.length()); for (StringTokenizer t = new StringTokenizer(packagePath, File.separator); t.hasMoreTokens(); ) { String s = t.nextToken(); if (!flex2.compiler.mxml.lang.TextParser.isValidIdentifier(s)) { return false; } } return true; } public SourcePathBase(String[] mimeTypes, boolean allowSourcePathOverlap) { this.mimeTypes = mimeTypes; this.allowSourcePathOverlap = allowSourcePathOverlap; sources = new HashMap<String, Source>(); hits = new HashSet<String>(); misses = new HashSet<String>(1024); dirs = new HashMap<String, String>(); warnings = new ArrayList<ClasspathOverlap>(5); } protected final String[] mimeTypes; protected final Map<String, Source> sources; protected boolean allowSourcePathOverlap; protected final Set<String> hits, misses; protected final HashMap<String, String> dirs; protected final List<ClasspathOverlap> warnings; /* public Source findSource(String namespaceURI, String localPart) throws CompilerException { assert localPart.indexOf('.') == -1 && localPart.indexOf('/') == -1 && localPart.indexOf(':') == -1 : "findSource(" + namespaceURI + "," + localPart + ") has bad localPart"; // classname format is a.b:c String className = CompilerAPI.constructClassName(namespaceURI, localPart); return findSource(className, namespaceURI, localPart); } protected Source findSource(String className, String namespaceURI, String localPart) throws CompilerException { if (misses.contains(className)) { return null; } Source s = sources.get(className); if (s == null) { if ((s = findFile(className, namespaceURI, localPart)) != null) { return s; } } CompilationUnit u = (s != null) ? s.getCompilationUnit() : null; if (s != null && !s.exists()) { sources.remove(className); s = null; } if (adjustDefinitionName(namespaceURI, localPart, s, u)) { u = null; s = null; } if (s != null && ((u != null && !u.isDone()) || s.isUpdated())) { // s.removeCompilationUnit(); } else if (s != null && u != null) { s = s.copy(); assert s != null; } if (s == null) { misses.add(className); } return s; } protected boolean adjustDefinitionName(String namespaceURI, String localPart, Source s, CompilationUnit u) { // If the compilation unit does exist and the top level definition name doesn't match // the specified class name, we don't count it as a match. if (s != null && u != null && u.topLevelDefinitions.size() == 1) { if (!u.topLevelDefinitions.contains(namespaceURI, localPart)) { String realName = (u.topLevelDefinitions.first()).toString(); sources.put(realName, s); misses.remove(realName); return true; } } return false; } abstract boolean checkPreference(Source s); protected abstract Source findFile(String className, String namespaceURI, String localPart) throws CompilerException; protected File findFile(File directory, String relativePath, String[] mimeTypes) throws CompilerException { File found = null; for (int k = 0, length = mimeTypes.length; k < length; k++) { File f = findFile(directory, relativePath, mimeTypes[k]); if (f != null && found == null) { found = f; // break; } else if (f != null) { throw new MoreThanOneComponentOfTheSameName(found.getAbsolutePath(), f.getAbsolutePath()); } } return found; } protected File findFile(File directory, String relativePath, String mimeType) { String fullPath = directory.getPath() + File.separator + relativePath; int lastSlash = fullPath.lastIndexOf(File.separator); String dir = null; if (lastSlash != -1) { dir = fullPath.substring(0, lastSlash); if (dirs.get(dir) == NO_DIR) { return null; } } String path = relativePath + MimeMappings.getExtension(mimeType); File f = FileUtil.openFile(directory, path); if ((f != null) && f.isFile() && FileUtil.getCanonicalPath(f).endsWith(path)) { return f; } else if (f != null && dir != null && !dirs.containsKey(dir)) { File p = f.getParentFile(); dirs.put(dir, p != null && p.isDirectory() ? dir : NO_DIR); } return null; } String[] checkClassNameFileName(Source s) { String defName = null, pathName = null; if (s.getOwner() == this) { QName def = s.getCompilationUnit().topLevelDefinitions.last(); defName = def.getLocalPart(); pathName = s.getShortName(); if (defName.equals(pathName)) { return null; } } return new String[] { pathName, defName }; } String[] checkPackageNameDirectoryName(Source s) { String defPackage = null, pathPackage = null; if (s.getOwner() == this) { QName def = s.getCompilationUnit().topLevelDefinitions.last(); defPackage = NameFormatter.normalizePackageName(def.getNamespace()); pathPackage = NameFormatter.toDot(s.getRelativePath(), '/'); if (defPackage.equals(pathPackage)) { return null; } } return new String[] { pathPackage, defPackage }; } protected String constructRelativePath(Source s) { // + 1 removes the leading / String relativePath = s.getName().substring(s.getPathRoot().getName().length() + 1); for (int k = 0, length = mimeTypes.length; k < length; k++) { String ext = MimeMappings.getExtension(mimeTypes[k]); if (relativePath.endsWith(ext)) { relativePath = relativePath.substring(0, relativePath.length() - ext.length()); return relativePath; } } assert false; return null; } // used by CompilerAPI.validateCompilationUnits()... not efficient, but we rarely call it... public void removeSource(Source s) { for (Iterator i = sources.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = (Map.Entry) i.next(); if (e.getValue() == s) { i.remove(); return; } } assert false : "couldn't find " + s; } public void clearCache() { hits.clear(); misses.clear(); dirs.clear(); } String[] getMimeTypes() { return mimeTypes; } public Map<String, Source> sources() { return sources; } public String toString() { StringBuilder buffer = new StringBuilder("SourcePath: \n"); Iterator<Source> iterator = sources.values().iterator(); while (iterator.hasNext()) { Source source = iterator.next(); buffer.append("\tsource = " + source + ", cu = " + source.getCompilationUnit() + "\n"); } return buffer.toString(); } public void displayWarnings() { for (int i = 0, size = warnings.size(); i < size; i++) { ThreadLocalToolkit.log(warnings.get(i)); } } */ // error messages public static class ClasspathOverlap extends CompilerMessage.CompilerWarning { private static final long serialVersionUID = -6314431057641028497L; public ClasspathOverlap(String path, String directory) { super(); this.cpath = path; this.directory = directory; } public final String cpath, directory; } public static class MoreThanOneComponentOfTheSameName extends CompilerMessage.CompilerInfo { private static final long serialVersionUID = 5943423934006966281L; public MoreThanOneComponentOfTheSameName(String file1, String file2) { super(); this.file1 = file1; this.file2 = file2; } public final String file1, file2; } }