blob: 3e8ad86b9da43fb8e4372044067fee0c46d7a601 [file] [log] [blame]
/*
*
* 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;
}
}