001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.cfg;
017:
018: import com.google.gwt.core.ext.TreeLogger;
019: import com.google.gwt.core.ext.UnableToCompleteException;
020: import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
021: import com.google.gwt.core.ext.typeinfo.TypeOracle;
022: import com.google.gwt.dev.jdt.CacheManager;
023: import com.google.gwt.dev.jdt.TypeOracleBuilder;
024: import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
025: import com.google.gwt.dev.util.Empty;
026: import com.google.gwt.dev.util.FileOracle;
027: import com.google.gwt.dev.util.FileOracleFactory;
028: import com.google.gwt.dev.util.Util;
029: import com.google.gwt.dev.util.FileOracleFactory.FileFilter;
030:
031: import org.apache.tools.ant.types.ZipScanner;
032:
033: import java.io.File;
034: import java.net.URL;
035: import java.util.ArrayList;
036: import java.util.Arrays;
037: import java.util.Comparator;
038: import java.util.HashMap;
039: import java.util.HashSet;
040: import java.util.Iterator;
041: import java.util.List;
042: import java.util.Map;
043: import java.util.Set;
044: import java.util.Map.Entry;
045:
046: /**
047: * Represents a module specification. In principle, this could be built without
048: * XML for unit tests.
049: */
050: public class ModuleDef implements PublicOracle {
051: /**
052: * Default to recursive inclusion of java files if no explicit include
053: * directives are specified.
054: */
055: private static final String[] DEFAULT_SOURCE_FILE_INCLUDES_LIST = new String[] { "**/*.java" };
056:
057: private static final Comparator<Map.Entry<String, ?>> REV_NAME_CMP = new Comparator<Map.Entry<String, ?>>() {
058: public int compare(Map.Entry<String, ?> entry1,
059: Map.Entry<String, ?> entry2) {
060: String key1 = entry1.getKey();
061: String key2 = entry2.getKey();
062: // intentionally reversed
063: return key2.compareTo(key1);
064: }
065: };
066:
067: public static boolean isValidModuleName(String moduleName) {
068: String[] parts = moduleName.split("\\.");
069: for (int i = 0; i < parts.length; i++) {
070: String part = parts[i];
071: if (!Util.isValidJavaIdent(part)) {
072: return false;
073: }
074: }
075: return true;
076: }
077:
078: private final ArrayList<URLCompilationUnitProvider> allCups = new ArrayList<URLCompilationUnitProvider>();
079:
080: private final Set<String> alreadySeenFiles = new HashSet<String>();
081:
082: private final CacheManager cacheManager = new CacheManager(
083: ".gwt-cache", new TypeOracle());
084:
085: private CompilationUnitProvider[] cups = new CompilationUnitProvider[0];
086:
087: private final List<String> entryPointTypeNames = new ArrayList<String>();
088:
089: private final Set<File> gwtXmlFiles = new HashSet<File>();
090:
091: private FileOracle lazyPublicOracle;
092:
093: private FileOracle lazySourceOracle;
094:
095: private TypeOracle lazyTypeOracle;
096:
097: private final long moduleDefCreationTime = System
098: .currentTimeMillis();
099:
100: private final String name;
101:
102: private final Properties properties = new Properties();
103:
104: private final FileOracleFactory publicPathEntries = new FileOracleFactory();
105:
106: private final Rules rules = new Rules();
107:
108: private final Scripts scripts = new Scripts();
109:
110: private final Map<String, String> servletClassNamesByPath = new HashMap<String, String>();
111:
112: private final FileOracleFactory sourcePathEntries = new FileOracleFactory();
113:
114: private final Styles styles = new Styles();
115:
116: public ModuleDef(String name) {
117: this .name = name;
118: }
119:
120: public synchronized void addEntryPointTypeName(String typeName) {
121: entryPointTypeNames.add(typeName);
122: }
123:
124: public void addGwtXmlFile(File xmlFile) {
125: gwtXmlFiles.add(xmlFile);
126: }
127:
128: public synchronized void addPublicPackage(String publicPackage,
129: String[] includeList, String[] excludeList,
130: boolean defaultExcludes, boolean caseSensitive) {
131:
132: if (lazyPublicOracle != null) {
133: throw new IllegalStateException("Already normalized");
134: }
135:
136: final ZipScanner scanner = getScanner(includeList, excludeList,
137: defaultExcludes, caseSensitive);
138:
139: // index from this package down
140: publicPathEntries.addRootPackage(publicPackage,
141: new FileFilter() {
142: public boolean accept(String name) {
143: return scanner.match(name);
144: }
145: });
146: }
147:
148: public void addSourcePackage(String sourcePackage,
149: String[] includeList, String[] excludeList,
150: boolean defaultExcludes, boolean caseSensitive) {
151: addSourcePackageImpl(sourcePackage, includeList, excludeList,
152: defaultExcludes, caseSensitive, false);
153: }
154:
155: public void addSourcePackageImpl(String sourcePackage,
156: String[] includeList, String[] excludeList,
157: boolean defaultExcludes, boolean caseSensitive,
158: boolean isSuperSource) {
159: if (lazySourceOracle != null) {
160: throw new IllegalStateException("Already normalized");
161: }
162:
163: if (includeList.length == 0) {
164: /*
165: * If no includes list was provided then, use the default.
166: */
167: includeList = DEFAULT_SOURCE_FILE_INCLUDES_LIST;
168: }
169:
170: final ZipScanner scanner = getScanner(includeList, excludeList,
171: defaultExcludes, caseSensitive);
172:
173: final FileFilter sourceFileFilter = new FileFilter() {
174: public boolean accept(String name) {
175: return scanner.match(name);
176: }
177: };
178:
179: if (isSuperSource) {
180: sourcePathEntries.addRootPackage(sourcePackage,
181: sourceFileFilter);
182: } else {
183: sourcePathEntries.addPackage(sourcePackage,
184: sourceFileFilter);
185: }
186: }
187:
188: public void addSuperSourcePackage(String super SourcePackage,
189: String[] includeList, String[] excludeList,
190: boolean defaultExcludes, boolean caseSensitive) {
191: addSourcePackageImpl(super SourcePackage, includeList,
192: excludeList, defaultExcludes, caseSensitive, true);
193: }
194:
195: public void clearEntryPoints() {
196: entryPointTypeNames.clear();
197: }
198:
199: public synchronized URL findPublicFile(String partialPath) {
200: return lazyPublicOracle.find(partialPath);
201: }
202:
203: public synchronized String findServletForPath(String actual) {
204: // Walk in backwards sorted order to find the longest path match first.
205: //
206: Set<Entry<String, String>> entrySet = servletClassNamesByPath
207: .entrySet();
208: Entry<String, String>[] entries = Util.toArray(Entry.class,
209: entrySet);
210: Arrays.sort(entries, REV_NAME_CMP);
211: for (int i = 0, n = entries.length; i < n; ++i) {
212: String mapping = entries[i].getKey();
213: /*
214: * Ensure that URLs that match the servlet mapping, including those that
215: * have additional path_info, get routed to the correct servlet.
216: *
217: * See "Inside Servlets", Second Edition, pg. 208
218: */
219: if (actual.equals(mapping)
220: || actual.startsWith(mapping + "/")) {
221: return entries[i].getValue();
222: }
223: }
224: return null;
225: }
226:
227: public String[] getAllPublicFiles() {
228: return lazyPublicOracle.getAllFiles();
229: }
230:
231: public CacheManager getCacheManager() {
232: return cacheManager;
233: }
234:
235: public synchronized CompilationUnitProvider[] getCompilationUnits() {
236: return cups;
237: }
238:
239: public synchronized String[] getEntryPointTypeNames() {
240: final int n = entryPointTypeNames.size();
241: return entryPointTypeNames.toArray(new String[n]);
242: }
243:
244: public synchronized String getFunctionName() {
245: return name.replace('.', '_');
246: }
247:
248: public synchronized String getName() {
249: return name;
250: }
251:
252: /**
253: * The properties that have been defined.
254: */
255: public synchronized Properties getProperties() {
256: return properties;
257: }
258:
259: /**
260: * Gets a reference to the internal rules for this module def.
261: */
262: public synchronized Rules getRules() {
263: return rules;
264: }
265:
266: /**
267: * Gets a reference to the internal scripts list for this module def.
268: */
269: public Scripts getScripts() {
270: return scripts;
271: }
272:
273: public synchronized String[] getServletPaths() {
274: return servletClassNamesByPath.keySet().toArray(Empty.STRINGS);
275: }
276:
277: /**
278: * Gets a reference to the internal styles list for this module def.
279: */
280: public Styles getStyles() {
281: return styles;
282: }
283:
284: public synchronized TypeOracle getTypeOracle(TreeLogger logger)
285: throws UnableToCompleteException {
286: if (lazyTypeOracle == null) {
287:
288: // Refresh the type oracle.
289: //
290: try {
291: String msg = "Analyzing source in module '" + name
292: + "'";
293: TreeLogger branch = logger.branch(TreeLogger.TRACE,
294: msg, null);
295: long before = System.currentTimeMillis();
296: TypeOracleBuilder builder = new TypeOracleBuilder(
297: getCacheManager());
298: CompilationUnitProvider[] currentCups = getCompilationUnits();
299: Arrays.sort(currentCups,
300: CompilationUnitProvider.LOCATION_COMPARATOR);
301:
302: TreeLogger subBranch = null;
303: if (branch.isLoggable(TreeLogger.DEBUG)) {
304: subBranch = branch.branch(TreeLogger.DEBUG,
305: "Adding compilation units...", null);
306: }
307:
308: for (int i = 0; i < currentCups.length; i++) {
309: CompilationUnitProvider cup = currentCups[i];
310: if (subBranch != null) {
311: subBranch.log(TreeLogger.DEBUG, cup
312: .getLocation(), null);
313: }
314: builder.addCompilationUnit(currentCups[i]);
315: }
316: lazyTypeOracle = builder.build(branch);
317: long after = System.currentTimeMillis();
318: branch.log(TreeLogger.TRACE, "Finished in "
319: + (after - before) + " ms", null);
320: } catch (UnableToCompleteException e) {
321: logger.log(TreeLogger.ERROR,
322: "Failed to complete analysis", null);
323: throw new UnableToCompleteException();
324: }
325:
326: // Sanity check the seed types and don't even start it they're missing.
327: //
328: boolean seedTypesMissing = false;
329: if (lazyTypeOracle.findType("java.lang.Object") == null) {
330: Util.logMissingTypeErrorWithHints(logger,
331: "java.lang.Object");
332: seedTypesMissing = true;
333: } else {
334: TreeLogger branch = logger.branch(TreeLogger.TRACE,
335: "Finding entry point classes", null);
336: String[] typeNames = getEntryPointTypeNames();
337: for (int i = 0; i < typeNames.length; i++) {
338: String typeName = typeNames[i];
339: if (lazyTypeOracle.findType(typeName) == null) {
340: Util.logMissingTypeErrorWithHints(branch,
341: typeName);
342: seedTypesMissing = true;
343: }
344: }
345: }
346:
347: if (seedTypesMissing) {
348: throw new UnableToCompleteException();
349: }
350: }
351:
352: return lazyTypeOracle;
353: }
354:
355: public boolean isGwtXmlFileStale() {
356: for (Iterator<File> iter = gwtXmlFiles.iterator(); iter
357: .hasNext();) {
358: File xmlFile = iter.next();
359: if ((!xmlFile.exists())
360: || (xmlFile.lastModified() > moduleDefCreationTime)) {
361: return true;
362: }
363: }
364: return false;
365: }
366:
367: /**
368: * For convenience in hosted mode, servlets can be automatically loaded and
369: * delegated to via {@link com.google.gwt.dev.shell.GWTShellServlet}. If a
370: * servlet is already mapped to the specified path, it is replaced.
371: *
372: * @param path the url path at which the servlet resides
373: * @param servletClassName the name of the servlet to publish
374: */
375: public synchronized void mapServlet(String path,
376: String servletClassName) {
377: servletClassNamesByPath.put(path, servletClassName);
378: }
379:
380: public synchronized void refresh(TreeLogger logger)
381: throws UnableToCompleteException {
382:
383: cacheManager.invalidateVolatileFiles();
384: lazyTypeOracle = null;
385: normalize(logger);
386: getTypeOracle(logger);
387: Util.invokeInaccessableMethod(TypeOracle.class,
388: "incrementReloadCount", new Class[] {}, lazyTypeOracle,
389: new Object[] {});
390: }
391:
392: /**
393: * Returns the URL for a source file if it is found; <code>false</code>
394: * otherwise.
395: *
396: * NOTE: this method is for testing only.
397: *
398: * @param partialPath
399: * @return
400: */
401: synchronized URL findSourceFile(String partialPath) {
402: return lazySourceOracle.find(partialPath);
403: }
404:
405: /**
406: * The final method to call when everything is setup. Before calling this
407: * method, several of the getter methods may not be called. After calling this
408: * method, the add methods may not be called.
409: *
410: * @param logger Logs the activity.
411: */
412: synchronized void normalize(TreeLogger logger) {
413: // Normalize property providers.
414: //
415: for (Iterator<Property> iter = getProperties().iterator(); iter
416: .hasNext();) {
417: Property prop = iter.next();
418: if (prop.getActiveValue() == null) {
419: // If there are more than one possible values, then create a provider.
420: // Otherwise, pretend the one value is an active value.
421: //
422: String[] knownValues = prop.getKnownValues();
423: assert (knownValues.length > 0);
424: if (knownValues.length > 1) {
425: if (prop.getProvider() == null) {
426: // Create a default provider.
427: //
428: prop.setProvider(new DefaultPropertyProvider(
429: this , prop));
430: }
431: } else {
432: prop.setActiveValue(knownValues[0]);
433: }
434: }
435: }
436:
437: // Create the source path.
438: //
439: TreeLogger branch = Messages.SOURCE_PATH_LOCATIONS.branch(
440: logger, null);
441: lazySourceOracle = sourcePathEntries.create(branch);
442:
443: if (lazySourceOracle.isEmpty()) {
444: branch
445: .log(
446: TreeLogger.WARN,
447: "No source path entries; expect subsequent failures",
448: null);
449: } else {
450: // Create the CUPs
451: String[] allFiles = lazySourceOracle.getAllFiles();
452: Set<String> files = new HashSet<String>();
453: files.addAll(Arrays.asList(allFiles));
454: files.removeAll(alreadySeenFiles);
455: for (Iterator<String> iter = files.iterator(); iter
456: .hasNext();) {
457: String fileName = iter.next();
458: int pos = fileName.lastIndexOf('/');
459: String packageName;
460: if (pos >= 0) {
461: packageName = fileName.substring(0, pos);
462: packageName = packageName.replace('/', '.');
463: } else {
464: packageName = "";
465: }
466: URL url = lazySourceOracle.find(fileName);
467: allCups.add(new URLCompilationUnitProvider(url,
468: packageName));
469: }
470: alreadySeenFiles.addAll(files);
471: this .cups = allCups.toArray(this .cups);
472: }
473:
474: // Create the public path.
475: //
476: branch = Messages.PUBLIC_PATH_LOCATIONS.branch(logger, null);
477: lazyPublicOracle = publicPathEntries.create(branch);
478: }
479:
480: private ZipScanner getScanner(String[] includeList,
481: String[] excludeList, boolean defaultExcludes,
482: boolean caseSensitive) {
483: /*
484: * Hijack Ant's ZipScanner to handle inclusions/exclusions exactly as Ant
485: * does. We're only using its pattern-matching capabilities; the code path
486: * I'm using never tries to hit the filesystem in Ant 1.6.5.
487: */
488: ZipScanner scanner = new ZipScanner();
489: if (includeList.length > 0) {
490: scanner.setIncludes(includeList);
491: }
492: if (excludeList.length > 0) {
493: scanner.setExcludes(excludeList);
494: }
495: if (defaultExcludes) {
496: scanner.addDefaultExcludes();
497: }
498: scanner.setCaseSensitive(caseSensitive);
499: scanner.init();
500:
501: return scanner;
502: }
503:
504: }
|