View Javadoc

1   /*
2    * AutoImportor.java
3    * 
4    * Copyright (c) 2006-2007 David Holroyd
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package uk.co.badgersinfoil.metaas.impl;
20  
21  import java.util.ArrayList;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Set;
26  import org.asdt.core.internal.antlr.AS3Parser;
27  import uk.co.badgersinfoil.metaas.dom.ASArg;
28  import uk.co.badgersinfoil.metaas.dom.ASClassType;
29  import uk.co.badgersinfoil.metaas.dom.ASInterfaceType;
30  import uk.co.badgersinfoil.metaas.dom.ASMember;
31  import uk.co.badgersinfoil.metaas.dom.ASMethod;
32  import uk.co.badgersinfoil.metaas.dom.ASPackage;
33  import uk.co.badgersinfoil.metaas.dom.ASType;
34  import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTree;
35  
36  
37  /**
38   * Modify all CompilationUnits in an ASProject so that fully qualified names
39   * in the code are replaced with unqualified names, and the appropriate
40   * import statements are added to the package declaration. 
41   */
42  public class AutoImporter {
43  
44  	private List visibleQNames = new ArrayList();
45  	
46  	// TODO: maintaining a list of names that *could* clash is not as nice
47  	// as actually working out which names *do* clash in a particular
48  	// compilation unit.  However, that would require two passes over the
49  	// compilation unit, so we're taking the simpler route for the mo.
50  	private Set possiblyAmbigLocalNames = new HashSet();
51  
52  	public void performAutoImport(ASTActionScriptProject project) {
53  		loadAccessableLanguageElements(project);
54  		processSources(project);
55  	}
56  
57  	private void loadAccessableLanguageElements(ASTActionScriptProject project) {
58  		final Set seenLocalNames = new HashSet();
59  		
60  		// look for definitions within the current project code itself
61  		ASVisitor visitor = new ASVisitor.Null() {
62  			public void visit(ASPackage pkg) {
63  				// TODO: will need to look for other top-level
64  				// elements, once metaas has support for them
65  				ASType type = pkg.getType();
66  				ASQName name = new ASQName(pkg.getName(), type.getName());
67  				visibleQNames.add(name);
68  				if (seenLocalNames.contains(name.getLocalName())) {
69  					possiblyAmbigLocalNames.add(name.getLocalName());
70  				} else {
71  					seenLocalNames.add(name.getLocalName());
72  				}
73  			}
74  		};
75  		new ASWalker(visitor).walk(project);
76  
77  		// handle project classpath definitions
78  		Iterator i = project.getResourceRoots().iterator();
79  		while (i.hasNext()) {
80  			ResourceRoot resourceRoot = (ResourceRoot)i.next();
81  			Iterator j = resourceRoot.getDefinitionQNames().iterator();
82  			while (j.hasNext()) {
83  				ASQName name = (ASQName)j.next();
84  				visibleQNames.add(name);
85  				if (seenLocalNames.contains(name.getLocalName())) {
86  					possiblyAmbigLocalNames.add(name.getLocalName());
87  				} else {
88  					seenLocalNames.add(name.getLocalName());
89  				}
90  			}
91  		}
92  	}
93  
94  
95  	private void processSources(ASTActionScriptProject project) {
96  		new ASWalker(new ImportSourceProcessor()).walk(project);
97  	}
98  
99  	private class ImportSourceProcessor extends ASVisitor.Null {
100 		private String currentPackageName;
101 		private ASQName currentTypeName;
102 		private ASPackage currentPackage;
103 		private Set currentPackageImports = new HashSet();
104 		private ASType currentType;
105 
106 		// TODO: handle local names that would be ambiguous if not
107 		// fully-qualified.
108 
109 		public void visit(ASPackage pkg) {
110 			currentPackage = pkg;
111 			currentPackageName = pkg.getName();
112 			inspectImports(pkg);
113 		}
114 
115 		private void inspectImports(ASPackage pkg) {
116 			currentPackageImports.clear();
117 			List imports = pkg.findImports();
118 			for (Iterator i=imports.iterator(); i.hasNext(); ) {
119 				ASQName imp = new ASQName((String)i.next());
120 				if (imp.getLocalName().equals("*")) {
121 					// expand, to list all the imported names
122 					importNamesFromPackage(imp.getPackagePrefix());
123 				} else {
124 					currentPackageImports.add(imp);
125 				}
126 			}
127 		}
128 
129 		private void importNamesFromPackage(String packagePrefix) {
130 			for (Iterator i=visibleQNames.iterator(); i.hasNext(); ) {
131 				ASQName name = (ASQName)i.next();
132 				if (packagePrefix.equals(name.getPackagePrefix())) {
133 					currentPackageImports.add(name);
134 				}
135 			}
136 		}
137 
138 		public void visit(ASType type) {
139 			currentTypeName = new ASQName(currentPackageName, type.getName());
140 			currentType = type;
141 		}
142 		
143 		public void visit(ASClassType clazz) {
144 			String superClassName = clazz.getSuperclass();
145 			String processed = processTypeName(superClassName);
146 			if (processed != null) {
147 				clazz.setSuperclass(processed);
148 			}
149 			List interfaceNames = clazz.getImplementedInterfaces();
150 			for (Iterator i=interfaceNames.iterator(); i.hasNext(); ) {
151 				String interfaceName = (String)i.next();
152 				processed = processTypeName(interfaceName);
153 				if (processed != null) {
154 					clazz.removeImplementedInterface(interfaceName);
155 					clazz.addImplementedInterface(processed);
156 				}
157 			}
158 		}
159 
160 		public void visit(ASInterfaceType iface) {
161 			List interfaceNames = iface.getSuperInterfaces();
162 			for (Iterator i=interfaceNames.iterator(); i.hasNext(); ) {
163 				String interfaceName = (String)i.next();
164 				String processed = processTypeName(interfaceName);
165 				if (processed != null) {
166 					iface.removeSuperInterface(interfaceName);
167 					iface.addSuperInterface(processed);
168 				}
169 			}
170 		}
171 
172 		public void visit(ASMember member) {
173 			String processedName = processTypeName(member.getType());
174 			if (processedName != null) {
175 				member.setType(processedName);
176 			}
177 		}
178 
179 		private String processTypeName(String typeName) {
180 			if (typeName != null) {
181 				ASQName qname = new ASQName(typeName);
182 				if (qname.isQualified()
183 				    && visibleQNames.contains(qname))
184 				{
185 					if (importRequired(qname)) {
186 						addImport(qname);
187 					}
188 					if (!possiblyAmbigLocalNames.contains(qname.getLocalName())) {
189 						return qname.getLocalName();
190 					}
191 				}
192 			}
193 			return null;
194 		}
195 
196 		private boolean importRequired(ASQName qname) {
197 			if (currentPackageName != null && currentPackageName.equals(qname.getPackagePrefix())) {
198 				return false;
199 			}
200 			return !currentPackageImports.contains(qname);
201 		}
202 
203 		private void addImport(ASQName qname) {
204 			currentPackage.addImport(qname.toString());
205 			currentPackageImports.add(qname);
206 		}
207 
208 		public void visit(ASMethod method) {
209 			// there's no visit(ASArg),
210 			processAllArgs(method);
211 			if (currentType instanceof ASClassType) {
212 				// the methods in an interface lack a body,
213 				processBody(method);
214 			}
215 		}
216 
217 		private void processAllArgs(ASMethod method) {
218 			List args = method.getArgs();
219 			for (Iterator i=args.iterator(); i.hasNext(); ) {
220 				processArg((ASArg)i.next());
221 			}
222 		}
223 
224 		private void processArg(ASArg arg) {
225 			String processedName = processTypeName(arg.getType());
226 			if (processedName != null) {
227 				arg.setType(processedName);
228 			}
229 		}
230 
231 		private void processBody(ASMethod method) {
232 			LinkedListTree ast = ((ASTASMethod)method).getAST();
233 			LinkedListTree body = (LinkedListTree)ast.getFirstChildWithType(AS3Parser.BLOCK);
234 			processTreeForImports(body);
235 		}
236 
237 		// metaas doesn't have any representations for things at the
238 		// statement / expression level, so now we have to walk the AST,
239 
240 		private void processTreeForImports(LinkedListTree ast) {
241 			if (processNodeForImports(ast)) {
242 				for (int i=0; i<ast.getChildCount(); i++) {
243 					LinkedListTree child = (LinkedListTree)ast.getChild(i);
244 					processTreeForImports(child);
245 				}
246 			}
247 		}
248 
249 		private boolean processNodeForImports(LinkedListTree ast) {
250 			switch (ast.getType()) {
251 			case AS3Parser.TYPE_SPEC:
252 				processTypeSpecForImports(ast);
253 				return false;
254 			case AS3Parser.IDENTIFIER:
255 				processIdentifierForImports(ast);
256 				return false;
257 			case AS3Parser.PROPERTY_OR_IDENTIFIER:
258 				processPropertyOrIdentifierForImports(ast);
259 				return false;
260 			}
261 			return true;
262 		}
263 
264 		private void processTypeSpecForImports(LinkedListTree ast) {
265 			String typeName = ASTUtils.typeSpecText(ast);
266 			String processedName = processTypeName(typeName);
267 			if (processedName != null) {
268 				LinkedListTree newTypeSpec = AS3FragmentParser.parseTypeSpec(processedName);
269 				ast.setChildWithTokens(0, newTypeSpec.getFirstChild());
270 			}
271 		}
272 
273 		private void processIdentifierForImports(LinkedListTree ast) {
274 System.out.println(ASTUtils.identText(ast));
275 		}
276 
277 		private ASQName processPropertyOrIdentifierForImports(LinkedListTree ast) {
278 //System.out.println("processPropertyOrIdentifierForImports("+ast.toStringTree()+")");
279 			LinkedListTree target = ast.getFirstChild();
280 			LinkedListTree name = ast.getLastChild();
281 			if (name.getType() != AS3Parser.IDENT) {
282 				return null;
283 			}
284 			ASQName qname = null;
285 			if (target.getType() == AS3Parser.PROPERTY_OR_IDENTIFIER) {
286 				ASQName tmp = processPropertyOrIdentifierForImports(target);
287 				if (tmp != null) {
288 					qname = new ASQName(tmp.toString(), name.getText());
289 				}
290 			} else if (target.getType() == AS3Parser.IDENT) {
291 				qname = new ASQName(target.getText(), name.getText());
292 			}
293 			if (qname == null) {
294 				ast.token.setType(AS3Parser.PROPERTY_ACCESS);
295 			} else {
296 				String processedName = processTypeName(qname.toString());
297 //System.out.println("  : processTypeName("+qname+") => "+processedName);
298 				if (processedName != null) {
299 //System.out.println("  : ast.start="+ast.getStartToken()+" ast.stop="+ast.getStopToken());
300 					int myIndex = ast.getParent().getIndexOfChild(ast);
301 					ast.getParent().setChildWithTokens(myIndex, ASTUtils.newAST(AS3Parser.IDENT, processedName));
302 					qname = null;
303 				}
304 			}
305 			return qname;
306 		}
307 	}
308 }