KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > apt > core > util > AptConfig


1 /*******************************************************************************
2  * Copyright (c) 2005, 2007 BEA Systems, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * jgarms@bea.com, wharley@bea.com - initial API and implementation
10  *
11  *******************************************************************************/

12 package org.eclipse.jdt.apt.core.util;
13
14 import java.io.File JavaDoc;
15 import java.util.Collection JavaDoc;
16 import java.util.HashMap JavaDoc;
17 import java.util.HashSet JavaDoc;
18 import java.util.LinkedHashSet JavaDoc;
19 import java.util.Map JavaDoc;
20 import java.util.Set JavaDoc;
21 import java.util.Map.Entry;
22 import java.util.regex.Pattern JavaDoc;
23
24 import org.eclipse.core.resources.IFolder;
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IResource;
27 import org.eclipse.core.resources.IWorkspaceRoot;
28 import org.eclipse.core.resources.ProjectScope;
29 import org.eclipse.core.resources.ResourcesPlugin;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IPath;
32 import org.eclipse.core.runtime.IStatus;
33 import org.eclipse.core.runtime.Path;
34 import org.eclipse.core.runtime.Platform;
35 import org.eclipse.core.runtime.preferences.DefaultScope;
36 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
37 import org.eclipse.core.runtime.preferences.IPreferencesService;
38 import org.eclipse.core.runtime.preferences.IScopeContext;
39 import org.eclipse.core.runtime.preferences.InstanceScope;
40 import org.eclipse.jdt.apt.core.internal.AnnotationProcessorFactoryLoader;
41 import org.eclipse.jdt.apt.core.internal.AptPlugin;
42 import org.eclipse.jdt.apt.core.internal.AptProject;
43 import org.eclipse.jdt.apt.core.internal.generatedfile.GeneratedSourceFolderManager;
44 import org.eclipse.jdt.apt.core.internal.util.FactoryPath;
45 import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil;
46 import org.eclipse.jdt.core.IClasspathEntry;
47 import org.eclipse.jdt.core.IJavaProject;
48 import org.eclipse.jdt.core.JavaCore;
49 import org.eclipse.jdt.core.JavaModelException;
50 import org.osgi.service.prefs.BackingStoreException;
51
52 /**
53  * Accesses configuration data for APT.
54  * Note that some of the code in org.eclipse.jdt.ui reads and writes settings
55  * data directly, rather than calling into the methods of this class.
56  *
57  * This class is static. Instances should not be constructed.
58  *
59  * Helpful information about the Eclipse preferences mechanism can be found at:
60  * http://dev.eclipse.org/viewcvs/index.cgi/~checkout~/platform-core-home/documents/user_settings/faq.html
61  */

62 public class AptConfig {
63     
64     /** regex to identify substituted token in path variables */
65     private static final String JavaDoc PATHVAR_TOKEN = "^%[^%/\\\\ ]+%.*"; //$NON-NLS-1$
66
/** path variable meaning "workspace root" */
67     private static final String JavaDoc PATHVAR_ROOT = "%ROOT%"; //$NON-NLS-1$
68
/** path variable meaning "project root" */
69     private static final String JavaDoc PATHVAR_PROJECTROOT = "%PROJECT.DIR%"; //$NON-NLS-1$
70

71     /*
72      * Hide constructor; this is a static object
73      */

74     private AptConfig() {}
75     
76     /**
77      * Add the equivalent of -Akey=val to the list of processor options.
78      * @param key must be a nonempty string. It should only include the key;
79      * that is, it should not start with "-A".
80      * @param jproj a project, or null to set the option workspace-wide.
81      * @param val can be null (equivalent to -Akey). This does not mean
82      * remove the key; for that functionality, @see #removeProcessorOption(IJavaProject, String).
83      */

84     public static void addProcessorOption(IJavaProject jproj, String JavaDoc key, String JavaDoc val) {
85         if (key == null || key.length() < 1) {
86             throw new IllegalArgumentException JavaDoc();
87         }
88         IScopeContext context = (null != jproj) ?
89                 new ProjectScope(jproj.getProject()) : new InstanceScope();
90         IEclipsePreferences node = context.getNode(AptPlugin.PLUGIN_ID + "/" + //$NON-NLS-1$
91
AptPreferenceConstants.APT_PROCESSOROPTIONS);
92         String JavaDoc nonNullVal = val == null ? AptPreferenceConstants.APT_NULLVALUE : val;
93         node.put(key, nonNullVal);
94         try {
95             node.flush();
96         } catch (BackingStoreException e) {
97             AptPlugin.log(e, "Unable to save annotation processor option" + key); //$NON-NLS-1$
98
}
99     }
100     
101     /**
102      * Remove an option from the list of processor options.
103      * @param jproj a project, or null to remove the option workspace-wide.
104      * @param key must be a nonempty string. It should only include the key;
105      * that is, it should not start with "-A".
106      */

107     public static void removeProcessorOption(IJavaProject jproj, String JavaDoc key) {
108         if (key == null || key.length() < 1) {
109             throw new IllegalArgumentException JavaDoc();
110         }
111         IScopeContext context = (null != jproj) ?
112                 new ProjectScope(jproj.getProject()) : new InstanceScope();
113         IEclipsePreferences node = context.getNode(AptPlugin.PLUGIN_ID + "/" + //$NON-NLS-1$
114
AptPreferenceConstants.APT_PROCESSOROPTIONS);
115         node.remove(key);
116         try {
117             node.flush();
118         } catch (BackingStoreException e) {
119             AptPlugin.log(e, "Unable to save annotation processor option" + key); //$NON-NLS-1$
120
}
121     }
122     
123     /**
124      * Get the options that are presented to annotation processors by the
125      * AnnotationProcessorEnvironment. Options are key/value pairs which
126      * are set in the project properties.
127      *
128      * Option values can begin with a percent-delimited token representing
129      * a classpath variable or one of several predefined values. The token
130      * must either be followed by a path delimiter, or be the entire value.
131      * Such tokens will be replaced with their resolved value. The predefined
132      * values are <code>%ROOT%</code>, which is replaced by the absolute pathname
133      * of the workspace root directory, and <code>%PROJECT.DIR%</code>, which
134      * will be replaced by the absolute pathname of the project root directory.
135      * For example, a value of <code>%ECLIPSE_HOME%/configuration/config.ini</code>
136      * might be resolved to <code>d:/eclipse/configuration/config.ini</code>.
137      *
138      * This method returns some options which are set programmatically but
139      * are not directly editable, are not displayed in the configuration GUI,
140      * and are not persisted to the preference store. This is meant to
141      * emulate the behavior of Sun's apt command-line tool, which passes
142      * most of its command line options to the processor environment. The
143      * programmatically set options are:
144      * <code>-classpath</code> [set to Java build path]
145      * <code>-sourcepath</code> [set to Java source path]
146      * <code>-s</code> [set to generated src dir]
147      * <code>-d</code> [set to binary output dir]
148      * <code>-target</code> [set to compiler target version]
149      * <code>-source</code> [set to compiler source version]
150      *
151      * There are some slight differences between the options returned by this
152      * method and the options returned from this implementation of @see
153      * AnnotationProcessorEnvironment#getOptions(). First, that method returns
154      * additional options which are only meaningful during a build, such as
155      * <code>phase</code>. Second, that method also adds alternate encodings
156      * of each option, to be compatible with a bug in Sun's apt implementation:
157      * specifically, for each option key="k", value="v", an additional option
158      * is created with key="-Ak=v", value=null. This includes the user-created
159      * options, but does not include the programmatically defined options listed
160      * above.
161      *
162      * @param jproj a project, or null to query the workspace-wide setting.
163      * @return a mutable, possibly empty, map of (key, value) pairs.
164      * The value part of a pair may be null (equivalent to "-Akey" on the Sun apt
165      * command line).
166      * The value part may contain spaces.
167      */

168     public static Map JavaDoc<String JavaDoc, String JavaDoc> getProcessorOptions(IJavaProject jproj) {
169         Map JavaDoc<String JavaDoc,String JavaDoc> rawOptions = getRawProcessorOptions(jproj);
170         // map is large enough to also include the programmatically generated options
171
Map JavaDoc<String JavaDoc, String JavaDoc> options = new HashMap JavaDoc<String JavaDoc, String JavaDoc>(rawOptions.size() + 6);
172         
173         // Resolve path metavariables like %ROOT%
174
for (Map.Entry JavaDoc<String JavaDoc, String JavaDoc> entry : rawOptions.entrySet()) {
175             String JavaDoc resolvedValue = resolveVarPath(jproj, entry.getValue());
176             String JavaDoc value = (resolvedValue == null) ? entry.getValue() : resolvedValue;
177             options.put(entry.getKey(), value);
178         }
179         
180         if (jproj == null) {
181             // there are no programmatically set options at the workspace level
182
return options;
183         }
184         
185         IWorkspaceRoot root = jproj.getProject().getWorkspace().getRoot();
186         
187         // Add sourcepath and classpath variables
188
try {
189             IClasspathEntry[] classpathEntries = jproj.getResolvedClasspath(true);
190             Set JavaDoc<String JavaDoc> classpath = new LinkedHashSet JavaDoc<String JavaDoc>();
191             Set JavaDoc<String JavaDoc> sourcepath = new LinkedHashSet JavaDoc<String JavaDoc>();
192             
193             // For projects on the classpath, loops can exist; need to make sure we
194
// don't loop forever
195
Set JavaDoc<IJavaProject> projectsProcessed = new HashSet JavaDoc<IJavaProject>();
196             projectsProcessed.add(jproj);
197             for (IClasspathEntry entry : classpathEntries) {
198                 int kind = entry.getEntryKind();
199                 if (kind == IClasspathEntry.CPE_LIBRARY) {
200                     IPath cpPath = entry.getPath();
201                     
202                     IResource res = root.findMember(cpPath);
203                     
204                     // If res is null, the path is absolute (it's an external jar)
205
if (res == null) {
206                         classpath.add(cpPath.toOSString());
207                     }
208                     else {
209                         // It's relative
210
classpath.add(res.getLocation().toOSString());
211                     }
212                 }
213                 else if (kind == IClasspathEntry.CPE_SOURCE) {
214                     IResource res = root.findMember(entry.getPath());
215                     if (res == null) {
216                         continue;
217                     }
218                     IPath srcPath = res.getLocation();
219                     if (srcPath == null) {
220                         continue;
221                     }
222                     
223                     sourcepath.add(srcPath.toOSString());
224                 }
225                 else if (kind == IClasspathEntry.CPE_PROJECT) {
226                     // Add the dependent project's build path and classpath to ours
227
IPath otherProjectPath = entry.getPath();
228                     IProject otherProject = root.getProject(otherProjectPath.segment(0));
229                     
230                     // Note: JavaCore.create() is safe, even if the project is null --
231
// in that case, we get null back
232
IJavaProject otherJavaProject = JavaCore.create(otherProject);
233                     
234                     // If it doesn't exist, ignore it
235
if (otherJavaProject != null && otherJavaProject.isOpen()) {
236                         addProjectClasspath(root, otherJavaProject, projectsProcessed, classpath);
237                     }
238                 }
239             }
240             // if you add options here, also add them in isAutomaticProcessorOption(),
241
// and document them in docs/reference/automatic_processor_options.html.
242

243             // Classpath and sourcepath
244
options.put("-classpath",convertPathCollectionToString(classpath)); //$NON-NLS-1$
245
options.put("-sourcepath", convertPathCollectionToString(sourcepath)); //$NON-NLS-1$
246

247             // Get absolute path for generated source dir
248
IFolder genSrcDir = jproj.getProject().getFolder(getGenSrcDir(jproj));
249             String JavaDoc genSrcDirString = genSrcDir.getRawLocation().toOSString();
250             options.put("-s", genSrcDirString); //$NON-NLS-1$
251

252             // Absolute path for bin dir as well
253
IPath binPath = jproj.getOutputLocation();
254             IResource binPathResource = root.findMember(binPath);
255             String JavaDoc binDirString;
256             if (binPathResource != null) {
257                 binDirString = root.findMember(binPath).getLocation().toOSString();
258             }
259             else {
260                 binDirString = binPath.toOSString();
261             }
262             options.put("-d", binDirString); //$NON-NLS-1$
263

264             String JavaDoc target = jproj.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
265             options.put("-target", target); //$NON-NLS-1$
266

267             String JavaDoc source = jproj.getOption(JavaCore.COMPILER_SOURCE, true);
268             options.put("-source", source); //$NON-NLS-1$
269
}
270         catch (JavaModelException jme) {
271             AptPlugin.log(jme, "Could not get the classpath for project: " + jproj); //$NON-NLS-1$
272
}
273         
274         return options;
275     }
276     
277     /**
278      * If the value starts with a path variable such as %ROOT%, replace it with
279      * the absolute path.
280      * @param value the value of a -Akey=value command option
281      */

282     private static String JavaDoc resolveVarPath(IJavaProject jproj, String JavaDoc value) {
283         if (value == null) {
284             return null;
285         }
286         // is there a token to substitute?
287
if (!Pattern.matches(PATHVAR_TOKEN, value)) {
288             return value;
289         }
290         IPath path = new Path(value);
291         String JavaDoc firstToken = path.segment(0);
292         // If it matches %ROOT%/project, it is a project-relative path.
293
if (PATHVAR_ROOT.equals(firstToken)) {
294             IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
295             IResource proj = root.findMember(path.segment(1));
296             if (proj == null) {
297                 return value;
298             }
299             // all is well; do the substitution
300
IPath relativePath = path.removeFirstSegments(2);
301             IPath absoluteProjPath = proj.getLocation();
302             IPath absoluteResPath = absoluteProjPath.append(relativePath);
303             return absoluteResPath.toOSString();
304         }
305         
306         // If it matches %PROJECT.DIR%/project, the path is relative to the current project.
307
if (jproj != null && PATHVAR_PROJECTROOT.equals(firstToken)) {
308             // all is well; do the substitution
309
IPath relativePath = path.removeFirstSegments(1);
310             IPath absoluteProjPath = jproj.getProject().getLocation();
311             IPath absoluteResPath = absoluteProjPath.append(relativePath);
312             return absoluteResPath.toOSString();
313         }
314         
315         // otherwise it's a classpath-var-based path.
316
String JavaDoc cpvName = firstToken.substring(1, firstToken.length() - 1);
317         IPath cpvPath = JavaCore.getClasspathVariable(cpvName);
318         if (cpvPath != null) {
319             IPath resolved = cpvPath.append(path.removeFirstSegments(1));
320             return resolved.toOSString();
321         }
322         else {
323             return value;
324         }
325     }
326     
327     // We need this as a separate method, as we'll put dependent projects' output
328
// on the classpath
329
private static void addProjectClasspath(
330             IWorkspaceRoot root,
331             IJavaProject otherJavaProject,
332             Set JavaDoc<IJavaProject> projectsProcessed,
333             Set JavaDoc<String JavaDoc> classpath) {
334         
335         // Check for cycles. If we've already seen this project,
336
// no need to go any further.
337
if (projectsProcessed.contains(otherJavaProject)) {
338             return;
339         }
340         projectsProcessed.add(otherJavaProject);
341         
342         try {
343             // Add the output directory first as a binary entry for other projects
344
IPath binPath = otherJavaProject.getOutputLocation();
345             IResource binPathResource = root.findMember(binPath);
346             String JavaDoc binDirString;
347             if (binPathResource != null) {
348                 binDirString = root.findMember(binPath).getLocation().toOSString();
349             }
350             else {
351                 binDirString = binPath.toOSString();
352             }
353             classpath.add(binDirString);
354             
355             // Now the rest of the classpath
356
IClasspathEntry[] classpathEntries = otherJavaProject.getResolvedClasspath(true);
357             for (IClasspathEntry entry : classpathEntries) {
358                 if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
359                     IPath cpPath = entry.getPath();
360                     
361                     IResource res = root.findMember(cpPath);
362                     
363                     // If res is null, the path is absolute (it's an external jar)
364
if (res == null) {
365                         classpath.add(cpPath.toOSString());
366                     }
367                     else {
368                         // It's relative
369
classpath.add(res.getLocation().toOSString());
370                     }
371                 }
372                 else if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
373                     IPath otherProjectPath = entry.getPath();
374                     IProject otherProject = root.getProject(otherProjectPath.segment(0));
375                     IJavaProject yetAnotherJavaProject = JavaCore.create(otherProject);
376                     if (yetAnotherJavaProject != null) {
377                         addProjectClasspath(root, yetAnotherJavaProject, projectsProcessed, classpath);
378                     }
379                 }
380                 // Ignore source types
381
}
382         }
383         catch (JavaModelException jme) {
384             AptPlugin.log(jme, "Failed to get the classpath for the following project: " + otherJavaProject); //$NON-NLS-1$
385
}
386     }
387     
388     private static String JavaDoc convertPathCollectionToString(Collection JavaDoc<String JavaDoc> paths) {
389         if (paths.size() == 0) {
390             return ""; //$NON-NLS-1$
391
}
392         StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
393         boolean first = true;
394         for (String JavaDoc path : paths) {
395             if (first) {
396                 first = false;
397             }
398             else {
399                 sb.append(File.pathSeparatorChar);
400             }
401             sb.append(path);
402         }
403         return sb.toString();
404     }
405
406     /**
407      * Set all the processor options in one call. This will delete any
408      * options that are not passed in, so callers who do not wish to
409      * destroy pre-existing options should use addProcessorOption() instead.
410      * @param options a map of keys to values. The keys should not include
411      * any automatic options (@see #isAutomaticProcessorOption(String)),
412      * and the "-A" should not be included. That is, to perform the
413      * equivalent of the apt command line "-Afoo=bar", use the key "foo"
414      * and the value "bar". Keys cannot contain spaces; values can
415      * contain anything at all. Keys cannot be null, but values can be.
416      */

417     public static void setProcessorOptions(Map JavaDoc<String JavaDoc, String JavaDoc> options, IJavaProject jproj) {
418         IScopeContext context = (null != jproj) ?
419                 new ProjectScope(jproj.getProject()) : new InstanceScope();
420
421         // TODO: this call is needed only for backwards compatibility with
422
// settings files previous to 2005.11.13. At some point it should be
423
// removed.
424
removeOldStyleSettings(context);
425
426         IEclipsePreferences node = context.getNode(AptPlugin.PLUGIN_ID + "/" + //$NON-NLS-1$
427
AptPreferenceConstants.APT_PROCESSOROPTIONS);
428         try {
429             node.clear();
430             for (Entry<String JavaDoc, String JavaDoc> option : options.entrySet()) {
431                 String JavaDoc nonNullVal = option.getValue() == null ?
432                         AptPreferenceConstants.APT_NULLVALUE : option.getValue();
433                 node.put(option.getKey(), nonNullVal);
434             }
435             node.flush();
436         } catch (BackingStoreException e) {
437             AptPlugin.log(e, "Unable to save annotation processor options"); //$NON-NLS-1$
438
}
439     }
440
441     /**
442      * Is the named option automatically generated in getProcessorOptions(),
443      * or did it come from somewhere else, such as a -A processor option?
444      * @param key the name of an AnnotationProcessorEnvironment option
445      * @return true if the option is automatically set.
446      */

447     public static boolean isAutomaticProcessorOption(String JavaDoc key) {
448         if ("-classpath".equals(key)) //$NON-NLS-1$
449
return true;
450         if ("-sourcepath".equals(key)) //$NON-NLS-1$
451
return true;
452         if ("-s".equals(key)) //$NON-NLS-1$
453
return true;
454         if ("-d".equals(key)) //$NON-NLS-1$
455
return true;
456         if ("-target".equals(key)) //$NON-NLS-1$
457
return true;
458         if ("-source".equals(key)) //$NON-NLS-1$
459
return true;
460         return false;
461     }
462     
463     /**
464      * Get the options that are presented to annotation processors by the
465      * AnnotationProcessorEnvironment. The -A and = are stripped out, so
466      * (key, value) is the equivalent of -Akey=value.
467      *
468      * This method differs from getProcessorOptions in that the options returned
469      * by this method do NOT include any programmatically set options. This
470      * method returns only the options that are persisted to the preference
471      * store and that are displayed in the configuration GUI.
472      *
473      * @param jproj a project, or null to query the workspace-wide setting.
474      * If jproj is not null, but the project has no per-project settings,
475      * this method will fall back to the workspace-wide settings.
476      * @return a mutable, possibly empty, map of (key, value) pairs.
477      * The value part of a pair may be null (equivalent to "-Akey").
478      * The value part can contain spaces, if it is quoted: -Afoo="bar baz".
479      */

480     public static Map JavaDoc<String JavaDoc, String JavaDoc> getRawProcessorOptions(IJavaProject jproj) {
481         Map JavaDoc<String JavaDoc, String JavaDoc> options = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
482         
483         // TODO: this code is needed only for backwards compatibility with
484
// settings files previous to 2005.11.13. At some point it should be
485
// removed.
486
// If an old-style setting exists, add it into the mix for backward
487
// compatibility.
488
options.putAll(getOldStyleRawProcessorOptions(jproj));
489         
490         // Fall back from project to workspace scope on an all-or-nothing basis,
491
// not value by value. (Never fall back to default scope; there are no
492
// default processor options.) We can't use IPreferencesService for this
493
// as we would normally do, because we don't know the names of the keys.
494
IScopeContext[] contexts;
495         if (jproj != null) {
496             contexts = new IScopeContext[] {
497                     new ProjectScope(jproj.getProject()), new InstanceScope() };
498         }
499         else {
500             contexts = new IScopeContext[] { new InstanceScope() };
501         }
502         for (IScopeContext context : contexts) {
503             IEclipsePreferences prefs = context.getNode(AptPlugin.PLUGIN_ID);
504             try {
505                 if (prefs.childrenNames().length > 0) {
506                     IEclipsePreferences procOptionsNode = context.getNode(
507                             AptPlugin.PLUGIN_ID + "/" + AptPreferenceConstants.APT_PROCESSOROPTIONS); //$NON-NLS-1$
508
if (procOptionsNode != null) {
509                         for (String JavaDoc key : procOptionsNode.keys()) {
510                             String JavaDoc nonNullVal = procOptionsNode.get(key, null);
511                             String JavaDoc val = AptPreferenceConstants.APT_NULLVALUE.equals(nonNullVal) ?
512                                     null : nonNullVal;
513                             options.put(key, val);
514                         }
515                         break;
516                     }
517                 }
518             } catch (BackingStoreException e) {
519                 AptPlugin.log(e, "Unable to load annotation processor options"); //$NON-NLS-1$
520
}
521         }
522         return options;
523     }
524     
525     /**
526      * TODO: this code is needed only for backwards compatibility with
527      * settings files previous to 2005.11.13. At some point it should be
528      * removed.
529      * Get the processor options as an APT-style string ("-Afoo=bar -Abaz=quux")
530      */

531     private static Map JavaDoc<String JavaDoc, String JavaDoc> getOldStyleRawProcessorOptions(IJavaProject jproj) {
532         Map JavaDoc<String JavaDoc, String JavaDoc> options;
533         String JavaDoc allOptions = getString(jproj, AptPreferenceConstants.APT_PROCESSOROPTIONS);
534         if (null == allOptions) {
535             options = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
536         }
537         else {
538             ProcessorOptionsParser op = new ProcessorOptionsParser(allOptions);
539             options = op.parse();
540         }
541         return options;
542     }
543     /**
544      * TODO: this code is needed only for backwards compatibility with
545      * settings files previous to 2005.11.13. At some point it should be
546      * removed.
547      *
548      * Used to parse an apt-style command line string into a map of key/value
549      * pairs.
550      * Parsing ignores errors and simply tries to gobble up as many well-formed
551      * pairs as it can find.
552      */

553     private static class ProcessorOptionsParser {
554         final String JavaDoc _s;
555         int _start; // everything before this is already parsed.
556
boolean _hasVal; // does the last key found have a value token?
557

558         public ProcessorOptionsParser(String JavaDoc s) {
559             _s = s;
560             _start = 0;
561             _hasVal = false;
562         }
563         
564         public Map JavaDoc<String JavaDoc, String JavaDoc> parse() {
565             Map JavaDoc<String JavaDoc, String JavaDoc> options = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
566             String JavaDoc key;
567             while (null != (key = parseKey())) {
568                 options.put(key, parseVal());
569             }
570             return options;
571         }
572         
573         /**
574          * Skip until a well-formed key (-Akey[=val]) is found, and
575          * return the key. Set _start to the beginning of the value,
576          * or to the first character after the end of the key and
577          * delimiter, for a valueless key. Set _hasVal according to
578          * whether a value was found.
579          * @return a key, or null if no well-formed keys can be found.
580          */

581         private String JavaDoc parseKey() {
582             String JavaDoc key;
583             int spaceAt = -1;
584             int equalsAt = -1;
585             
586             _hasVal = false;
587             
588             while (true) {
589                 _start = _s.indexOf("-A", _start); //$NON-NLS-1$
590
if (_start < 0) {
591                     return null;
592                 }
593                 
594                 // we found a -A. The key is everything up to the next '=' or ' ' or EOL.
595
_start += 2;
596                 if (_start >= _s.length()) {
597                     // it was just a -A, nothing following.
598
return null;
599                 }
600                 
601                 spaceAt = _s.indexOf(' ', _start);
602                 equalsAt = _s.indexOf('=', _start);
603                 if (spaceAt == _start || equalsAt == _start) {
604                     // false alarm. Keep trying.
605
++_start;
606                     continue;
607                 }
608                 break;
609             }
610             
611             // We found a legitimate -A with some text after it.
612
// Where does the key end?
613
if (equalsAt > 0) {
614                 if (spaceAt < 0 || equalsAt < spaceAt) {
615                     // there is an equals, so there is a value.
616
key = new String JavaDoc(_s.substring(_start, equalsAt));
617                     _start = equalsAt + 1;
618                     _hasVal = (_start < _s.length());
619                 }
620                 else {
621                     // the next thing is a space, so this is a valueless key
622
key = new String JavaDoc(_s.substring(_start, spaceAt));
623                     _start = spaceAt + 1;
624                 }
625             }
626             else {
627                 if (spaceAt < 0) {
628                     // no equals sign and no spaces: a valueless key, up to the end of the string.
629
key = new String JavaDoc(_s.substring(_start));
630                     _start = _s.length();
631                 }
632                 else {
633                     // the next thing is a space, so this is a valueless key
634
key = new String JavaDoc(_s.substring(_start, spaceAt));
635                     _start = spaceAt + 1;
636                 }
637             }
638             return key;
639         }
640         
641         /**
642          * A value token is delimited by a space; but spaces inside quoted
643          * regions are ignored. A value may include multiple quoted regions.
644          * An unmatched quote is treated as if there was a matching quote at
645          * the end of the string. Quotes are returned as part of the value.
646          * @return the value, up to the next nonquoted space or end of string.
647          */

648         private String JavaDoc parseVal() {
649             if (!_hasVal || _start < 0 || _start >= _s.length()) {
650                 return null;
651             }
652             boolean inQuotedRegion = false;
653             int start = _start;
654             int end = _start;
655             while (end < _s.length()) {
656                 char c = _s.charAt(end);
657                 if (c == '"') {
658                     inQuotedRegion = !inQuotedRegion;
659                 }
660                 else if (!inQuotedRegion && c == ' ') {
661                     // end of token.
662
_start = end + 1;
663                     break;
664                 }
665                 ++end;
666             }
667  
668             return new String JavaDoc(_s.substring(start, end));
669         }
670     }
671     
672     /**
673      * TODO: this code is needed only for backwards compatibility with
674      * settings files previous to 2005.11.13. At some point it should be
675      * removed.
676      * Delete the key that saves annotation processor options as a single
677      * command-line-type string ("-Afoo=bar -Abaz=quux").
678      */

679     private static void removeOldStyleSettings(IScopeContext context) {
680         IEclipsePreferences node = context.getNode(AptPlugin.PLUGIN_ID);
681         node.remove(AptPreferenceConstants.APT_PROCESSOROPTIONS);
682     }
683
684     /**
685      * Flush unsaved preferences and perform any other config-related shutdown.
686      * This is called once, from AptPlugin.shutdown().
687      */

688     public static void dispose() {
689         try {
690             new InstanceScope().getNode(AptPlugin.PLUGIN_ID).flush();
691         }
692         catch (BackingStoreException e) {
693             // log failure and continue
694
AptPlugin.log(e, "Couldn't flush preferences to disk"); //$NON-NLS-1$
695
}
696     }
697
698     /**
699      * Initialize preferences lookups, and register change listeners.
700      * This is called once, from AptPlugin.startup().
701      */

702     public static void initialize() {
703         // If we cached workspace-level preferences, we would want to install
704
// some change listeners here.
705
}
706     
707     /**
708      * Is annotation processing turned on for this project?
709      * <p>
710      * Prior to Eclipse 3.3, this read the org.eclipse.jdt.apt.aptEnabled
711      * setting. In Eclipse 3.3, it reads the org.eclipse.jdt.core.compiler.processingEnabled
712      * setting; the result is logically or-ed with value of the older setting in order to
713      * preserve backward compatibility.
714      * @param jproject an IJavaProject, or null to request workspace preferences.
715      * @return true if annotation processing is turned on.
716      */

717     public static boolean isEnabled(IJavaProject jproject) {
718         if ("enabled".equals(getString(jproject, AptPreferenceConstants.APT_PROCESSANNOTATIONS))) { //$NON-NLS-1$
719
return true;
720         }
721         // backward compatibility: also return true if old setting is enabled
722
return getBoolean(jproject, AptPreferenceConstants.APT_ENABLED);
723     }
724     
725     
726     /**
727      * Turn annotation processing on or off for this project.
728      * <p>
729      * Prior to Eclipse 3.3, this affected the org.eclipse.jdt.apt.aptEnabled
730      * setting. In Eclipse 3.3, it affects the org.eclipse.jdt.core.compiler.processingEnabled
731      * setting; the older setting is still set (and read) in order to preserve backward
732      * compatibility.
733      * @param jproject an IJavaProject, or null to set workspace preferences.
734      * @param enabled
735      */

736     public static void setEnabled(IJavaProject jproject, boolean enabled) {
737         if (jproject == null && enabled == true) {
738             IllegalArgumentException JavaDoc e = new IllegalArgumentException JavaDoc();
739             IStatus status = AptPlugin.createWarningStatus(e,
740                 "Illegal attempt to enable annotation processing workspace-wide"); //$NON-NLS-1$
741
AptPlugin.log(status);
742             throw e;
743         }
744         setString(jproject, AptPreferenceConstants.APT_PROCESSANNOTATIONS,
745                 enabled ? AptPreferenceConstants.ENABLED : AptPreferenceConstants.DISABLED);
746         // backward compatibility: also save old setting
747
setBoolean(jproject, AptPreferenceConstants.APT_ENABLED, enabled);
748     }
749     
750     /**
751      * Is annotation processing turned on during reconcile, or only during build?
752      * Note that if isEnabled() is false, processing will not occur at all; the
753      * two settings are independent.
754      * @param jproject an IJavaProject to query, or null to get the default value.
755      * @return true if processing is enabled during both reconcile and build
756      */

757     public static boolean shouldProcessDuringReconcile(IJavaProject jproject) {
758         return getBoolean(jproject, AptPreferenceConstants.APT_RECONCILEENABLED);
759     }
760     
761     /**
762      * Turn processing during reconcile on or off. Processing during build is
763      * unaffected. Note that if isEnabled() is false, processing will not occur
764      * at all; the two settings are independent.
765      * @param jproject the IJavaProject to modify. This setting is only valid
766      * on individual projects.
767      */

768     public static void setProcessDuringReconcile(IJavaProject jproject, boolean enabled) {
769         setBoolean(jproject, AptPreferenceConstants.APT_RECONCILEENABLED, enabled);
770     }
771     
772     private static boolean getBoolean(IJavaProject jproj, String JavaDoc optionName) {
773         IPreferencesService service = Platform.getPreferencesService();
774         IScopeContext[] contexts;
775         if (jproj != null) {
776             contexts = new IScopeContext[] {
777                     new ProjectScope(jproj.getProject()), new InstanceScope(), new DefaultScope() };
778         }
779         else {
780             contexts = new IScopeContext[] { new InstanceScope(), new DefaultScope() };
781         }
782         return service.getBoolean(
783                 AptPlugin.PLUGIN_ID,
784                 optionName,
785                 Boolean.parseBoolean(AptPreferenceConstants.DEFAULT_OPTIONS_MAP.get(optionName)),
786                 contexts);
787     }
788     
789     /**
790      * Get a factory path corresponding to the default values: if jproj is
791      * non-null, return the current workspace factory path (workspace prefs
792      * are the default for a project); if jproj is null, return the default
793      * list of plugin factories (which is the "factory default").
794      */

795     public static IFactoryPath getDefaultFactoryPath(IJavaProject jproj) {
796         return FactoryPathUtil.getDefaultFactoryPath(jproj);
797     }
798     
799     /**
800      * Get the factory path for a given project or for the workspace.
801      * @param jproj the project, or null to get the factory path for the workspace.
802      * @return a FactoryPath representing the current state of the specified project.
803      * Note that changes made to the project after this call will not affect the
804      * returned object - that is, it behaves like a value, not like a live link to
805      * the project state.
806      */

807     public static IFactoryPath getFactoryPath(IJavaProject jproj) {
808         return FactoryPathUtil.getFactoryPath(jproj);
809     }
810     
811     /**
812      * Set the factory path for a given project or for the workspace.
813      * Does not perform any validation on the path.
814      * @param jproj the project, or null to set the factory path for the workspace.
815      * @param path a factory path, or null to reset the factory path to the default.
816      */

817     public static void setFactoryPath(IJavaProject jproj, IFactoryPath path)
818             throws CoreException
819     {
820         FactoryPath fp = (FactoryPath)path;
821         FactoryPathUtil.setFactoryPath(jproj, fp);
822         // Project-specific factory path files are resources, so changes
823
// get picked up by the resource listener. Workspace changes aren't.
824
if (jproj == null) {
825             AnnotationProcessorFactoryLoader.getLoader().resetAll();
826         }
827     }
828     
829     /**
830      * Has an explicit factory path been set for the specified project, or
831      * is it just defaulting to the workspace settings?
832      * @return true if there is a project-specific factory path.
833      */

834     public static boolean hasProjectSpecificFactoryPath(IJavaProject jproj) {
835         if (null == jproj) {
836             // say no, even if workspace-level factory path does exist.
837
return false;
838         }
839         return FactoryPathUtil.doesFactoryPathFileExist(jproj);
840     }
841
842     /**
843      * Helper method to get a single preference setting, e.g., APT_GENSRCDIR.
844      * This is a different level of abstraction than the processor -A settings!
845      * The -A settings are all contained under one single preference node,
846      * APT_PROCESSOROPTIONS. Use @see #getProcessorOptions(IJavaProject) to
847      * get the -A settings; use @see #getOptions(IJavaProject) to get all the
848      * preference settings as a map; and use this helper method to get a single
849      * preference setting.
850      *
851      * @param jproj the project, or null for workspace.
852      * @param optionName a preference constant from @see AptPreferenceConstants.
853      * @return the string value of the setting.
854      */

855     public static String JavaDoc getString(IJavaProject jproj, String JavaDoc optionName) {
856         IPreferencesService service = Platform.getPreferencesService();
857         IScopeContext[] contexts;
858         if (jproj != null) {
859             contexts = new IScopeContext[] {
860                     new ProjectScope(jproj.getProject()), new InstanceScope(), new DefaultScope() };
861         }
862         else {
863             contexts = new IScopeContext[] { new InstanceScope(), new DefaultScope() };
864         }
865         String JavaDoc pluginId = null;
866         if (AptPreferenceConstants.APT_PROCESSANNOTATIONS.equals(optionName)) {
867             pluginId = JavaCore.PLUGIN_ID;
868         }
869         else {
870             pluginId = AptPlugin.PLUGIN_ID;
871         }
872         return service.getString(
873                 pluginId,
874                 optionName,
875                 AptPreferenceConstants.DEFAULT_OPTIONS_MAP.get(optionName),
876                 contexts);
877     }
878     
879     public static String JavaDoc getGenSrcDir(IJavaProject jproject) {
880         return getString(jproject, AptPreferenceConstants.APT_GENSRCDIR);
881     }
882     
883     public static void setGenSrcDir(IJavaProject jproject, String JavaDoc dirString) {
884         if (!GeneratedSourceFolderManager.validate(jproject, dirString)) {
885             throw new IllegalArgumentException JavaDoc("Illegal name for generated source folder: " + dirString); //$NON-NLS-1$
886
}
887         setString(jproject, AptPreferenceConstants.APT_GENSRCDIR, dirString);
888     }
889     
890     public static boolean validateGenSrcDir(IJavaProject jproject, String JavaDoc dirName) {
891         return GeneratedSourceFolderManager.validate(jproject, dirName);
892     }
893     
894     private static void setBoolean(IJavaProject jproject, String JavaDoc optionName, boolean value) {
895         IScopeContext context = (null != jproject) ?
896                 new ProjectScope(jproject.getProject()) : new InstanceScope();
897         IEclipsePreferences node = context.getNode(AptPlugin.PLUGIN_ID);
898         // get old val as a String, so it can be null if setting doesn't exist yet
899
String JavaDoc oldValue = node.get(optionName, null);
900         node.putBoolean(optionName, value);
901         if (jproject != null && oldValue == null || (value != Boolean.parseBoolean(oldValue))) {
902             AptProject aproj = AptPlugin.getAptProject(jproject);
903             aproj.preferenceChanged(optionName);
904         }
905         flushPreference(optionName, node);
906     }
907     
908     private static void setString(IJavaProject jproject, String JavaDoc optionName, String JavaDoc value) {
909         IScopeContext context = (null != jproject) ?
910                 new ProjectScope(jproject.getProject()) : new InstanceScope();
911         IEclipsePreferences node;
912         if (AptPreferenceConstants.APT_PROCESSANNOTATIONS.equals(optionName)) {
913             node = context.getNode(JavaCore.PLUGIN_ID);
914         }
915         else {
916             node = context.getNode(AptPlugin.PLUGIN_ID);
917         }
918         String JavaDoc oldValue = node.get(optionName, null);
919         node.put(optionName, value);
920         if (jproject != null && !value.equals(oldValue)) {
921             AptProject aproj = AptPlugin.getAptProject(jproject);
922             aproj.preferenceChanged(optionName);
923         }
924         flushPreference(optionName, node);
925     }
926
927     private static void flushPreference(String JavaDoc optionName, IEclipsePreferences node) {
928         try {
929             node.flush();
930         }
931         catch (BackingStoreException e){
932             AptPlugin.log(e, "Failed to save preference: " + optionName); //$NON-NLS-1$
933
}
934     }
935
936 }
937
Popular Tags