/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.eclipse.ui.convert;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import com.windowtester.ui.util.Logger;
/**
* Call {@link #buildContext(CompilationUnit)} to build {@link WTConvertAPIContext} by
* visiting the package statement and all import statements looking for references to
* WindowTester types.
*/
public class WTConvertAPIContextBuilder extends ASTVisitor
{
/**
* The compiler options used when parsing java source
*/
private static Hashtable<String, String> compilerOptions;
/**
* Answer the lazily initialized compiler options used when parsing java source
*/
@SuppressWarnings("unchecked")
private Hashtable<String, String> getCompilerOptions() {
if (compilerOptions == null) {
compilerOptions = new Hashtable<String, String>(JavaCore.getDefaultOptions());
compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5);
}
return compilerOptions;
}
private Set<String> wtTypeNames;
private Set<String> wtStaticTypeNames;
/**
* Traverse the AST structure and return a context containing the imported types and
* members. This method is preferred over {@link #buildContext(String)} because the
* compilation unit passed in carries the compiler options with it where as
* {@link #buildContext(String)} uses default compiler options.
*
* @param compUnit the compilation unit's AST structure (not <code>null</code>)
* @return the context (not <code>null</code>)
*/
public WTConvertAPIContext buildContext(ICompilationUnit compUnit) throws JavaModelException {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(compUnit);
return buildContext(parser, compUnit.getSource());
}
/**
* Traverse the AST structure and return a context containing the imported types and
* members. The {@link #buildContext(ICompilationUnit)} method is preferred because
* the compilation unit carries the compiler options where as this method uses default
* compiler options. This method is used for testing.
*
* @param compUnit the compilation unit's AST structure (not <code>null</code>)
* @return the context (not <code>null</code>)
*/
public WTConvertAPIContext buildContext(String source) {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setCompilerOptions(getCompilerOptions());
parser.setSource(source.toCharArray());
return buildContext(parser, source);
}
private WTConvertAPIContext buildContext(ASTParser parser, String source) {
// Parse the source
loadWTInfo();
CompilationUnit compUnit = (CompilationUnit) parser.createAST(new NullProgressMonitor());
// Throw an exception if any syntax errors are detected
for (IProblem problem : compUnit.getProblems())
throw new WTConvertAPIParseException(problem);
// Initialize the receiver and traverse the parse tree
wtTypeNames = new HashSet<String>();
wtStaticTypeNames = new HashSet<String>();
compUnit.accept(this);
// Return the context
return new WTConvertAPIContext(source, compUnit, wtTypeNames, wtStaticTypeNames);
}
/**
* Collect the imported WindowTester types
*/
public boolean visit(PackageDeclaration node) {
String fullyQualifiedName = node.getName().getFullyQualifiedName();
Collection<String> typeNamesInPackage = WT_TYPE_NAME_MAP.get(fullyQualifiedName);
if (typeNamesInPackage != null)
wtTypeNames.addAll(typeNamesInPackage);
return false;
}
/**
* Collect the imported WindowTester types
*/
public boolean visit(ImportDeclaration node) {
String fullyQualifiedName = node.getName().getFullyQualifiedName();
if (node.isStatic()) {
if (node.isOnDemand()) {
Collection<String> memberNamesInType = WT_MEMBER_NAME_MAP.get(fullyQualifiedName);
if (memberNamesInType != null)
wtStaticTypeNames.addAll(memberNamesInType);
}
else {
wtStaticTypeNames.add(fullyQualifiedName);
}
}
else {
if (node.isOnDemand()) {
Collection<String> typeNamesInPackage = WT_TYPE_NAME_MAP.get(fullyQualifiedName);
if (typeNamesInPackage != null)
wtTypeNames.addAll(typeNamesInPackage);
}
else {
if (isWTType(fullyQualifiedName))
wtTypeNames.add(fullyQualifiedName);
}
}
return false;
}
public boolean visit(TypeDeclaration node) {
return false;
}
//================================================================================
// Global WindowTester type information
/**
* A collection of fully qualified WindowTester type names
*/
private static Set<String> WT_TYPE_NAMES = null;
/**
* A collection of fully qualified WindowTester member names
*/
private static Set<String> WT_MEMBER_NAMES = null;
/**
* A mapping of package name to fully qualified WindowTester type names in that
* package
*/
private static Map<String, Collection<String>> WT_TYPE_NAME_MAP = null;
/**
* A mapping of type name to fully qualified WindowTester member names in that
* type
*/
private static Map<String, Collection<String>> WT_MEMBER_NAME_MAP = null;
/**
* Read the wt-types.txt and wt-static-members.txt files and cache that information in
* this class for rapid access.
*/
private static void loadWTInfo() {
if (WT_TYPE_NAMES != null)
return;
WT_TYPE_NAMES = new HashSet<String>();
WT_TYPE_NAME_MAP = new HashMap<String, Collection<String>>();
loadWTInfo("wt-types.txt", WT_TYPE_NAMES, WT_TYPE_NAME_MAP);
WT_MEMBER_NAMES = new HashSet<String>();
WT_MEMBER_NAME_MAP = new HashMap<String, Collection<String>>();
loadWTInfo("wt-static-members.txt", WT_MEMBER_NAMES, WT_MEMBER_NAME_MAP);
}
private static void loadWTInfo(String fileName, Set<String> names, Map<String, Collection<String>> nameMap) {
InputStream stream = WTConvertAPIContextBuilder.class.getResourceAsStream(fileName);
try {
LineNumberReader reader = new LineNumberReader(new InputStreamReader(new BufferedInputStream(stream)));
while (true) {
String typeName = reader.readLine();
if (typeName == null)
break;
typeName = typeName.trim();
if (typeName.length() == 0 || typeName.charAt(0) == '#')
continue;
names.add(typeName);
int index = typeName.lastIndexOf('.');
if (index == -1)
continue;
String packageName = typeName.substring(0, index);
Collection<String> typeNamesInPackage = nameMap.get(packageName);
if (typeNamesInPackage == null) {
typeNamesInPackage = new ArrayList<String>();
nameMap.put(packageName, typeNamesInPackage);
}
typeNamesInPackage.add(typeName);
}
}
catch (IOException e) {
Logger.log("Failed to read " + fileName + " stream", e);
}
finally {
try {
stream.close();
}
catch (IOException e) {
Logger.log("Failed to close " + fileName + " stream", e);
}
}
}
/**
* Answer true if the specified fully qualified type name is a WindowTester type
*
* @param fullyQualifiedTypeName the type name
* @return <code>true</code> if the type name references a WindowTester type
*/
public static boolean isWTType(String fullyQualifiedTypeName) {
loadWTInfo();
return WT_TYPE_NAMES.contains(fullyQualifiedTypeName);
}
/**
* Answer the fully qualified names of the WindowTester types in the specified package
* or <code>null</code> if the specified package is not part of the WindowTester
* product
*/
public static Collection<String> getWTTypesInPackage(String packageName) {
loadWTInfo();
return WT_TYPE_NAME_MAP.get(packageName);
}
}