Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2008 Sun Microsystems, Inc. All rights reserved. |
5 |
* |
6 |
* The contents of this file are subject to the terms of either the GNU |
7 |
* General Public License Version 2 only ("GPL") or the Common |
8 |
* Development and Distribution License("CDDL") (collectively, the |
9 |
* "License"). You may not use this file except in compliance with the |
10 |
* License. You can obtain a copy of the License at |
11 |
* http://www.netbeans.org/cddl-gplv2.html |
12 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
13 |
* specific language governing permissions and limitations under the |
14 |
* License. When distributing the software, include this License Header |
15 |
* Notice in each file and include the License file at |
16 |
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this |
17 |
* particular file as subject to the "Classpath" exception as provided |
18 |
* by Sun in the GPL Version 2 section of the License file that |
19 |
* accompanied this code. If applicable, add the following below the |
20 |
* License Header, with the fields enclosed by brackets [] replaced by |
21 |
* your own identifying information: |
22 |
* "Portions Copyrighted [year] [name of copyright owner]" |
23 |
* |
24 |
* If you wish your version of this file to be governed by only the CDDL |
25 |
* or only the GPL Version 2, indicate your decision by adding |
26 |
* "[Contributor] elects to include this software in this distribution |
27 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
28 |
* single choice of license, a recipient has the option to distribute |
29 |
* your version of this file under either the CDDL, the GPL Version 2 or |
30 |
* to extend the choice of license to its licensees as provided above. |
31 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
32 |
* Version 2 license, then the option applies only if the new code is |
33 |
* made subject to such option by the copyright holder. |
34 |
* |
35 |
* Contributor(s): |
36 |
* |
37 |
* Portions Copyrighted 2008 Sun Microsystems, Inc. |
38 |
*/ |
39 |
|
40 |
package org.openide.filesystems.annotations; |
41 |
|
42 |
import java.io.ByteArrayInputStream; |
43 |
import java.io.ByteArrayOutputStream; |
44 |
import java.io.FileNotFoundException; |
45 |
import java.io.IOException; |
46 |
import java.io.InputStream; |
47 |
import java.io.OutputStream; |
48 |
import java.util.ArrayList; |
49 |
import java.util.Arrays; |
50 |
import java.util.List; |
51 |
import java.util.Map; |
52 |
import java.util.Set; |
53 |
import java.util.SortedSet; |
54 |
import java.util.TreeSet; |
55 |
import java.util.WeakHashMap; |
56 |
import javax.annotation.processing.AbstractProcessor; |
57 |
import javax.annotation.processing.ProcessingEnvironment; |
58 |
import javax.annotation.processing.RoundEnvironment; |
59 |
import javax.lang.model.element.Element; |
60 |
import javax.lang.model.element.ExecutableElement; |
61 |
import javax.lang.model.element.Modifier; |
62 |
import javax.lang.model.element.TypeElement; |
63 |
import javax.lang.model.type.TypeMirror; |
64 |
import javax.lang.model.util.ElementFilter; |
65 |
import javax.tools.Diagnostic.Kind; |
66 |
import javax.tools.FileObject; |
67 |
import javax.tools.StandardLocation; |
68 |
import org.openide.filesystems.XMLFileSystem; |
69 |
import org.openide.xml.XMLUtil; |
70 |
import org.w3c.dom.Document; |
71 |
import org.w3c.dom.NodeList; |
72 |
import org.xml.sax.EntityResolver; |
73 |
import org.xml.sax.ErrorHandler; |
74 |
import org.xml.sax.InputSource; |
75 |
import org.xml.sax.SAXException; |
76 |
import org.xml.sax.SAXParseException; |
77 |
|
78 |
/** |
79 |
* Convenience base class for an annotation processor which creates XML layer entries. |
80 |
* @see XMLFileSystem |
81 |
*/ |
82 |
public abstract class LayerGeneratingProcessor extends AbstractProcessor { |
83 |
|
84 |
private static final String GENERATED_LAYER = "META-INF/generated-layer.xml"; |
85 |
private static final String PUBLIC_DTD_ID = "-//NetBeans//DTD Filesystem 1.2//EN"; |
86 |
private static final String NETWORK_DTD_URL = "http://www.netbeans.org/dtds/filesystem-1_2.dtd"; |
87 |
private static final String LOCAL_DTD_RESOURCE = "/org/openide/filesystems/filesystem1_2.dtd"; |
88 |
|
89 |
private static final ErrorHandler ERROR_HANDLER = new ErrorHandler() { |
90 |
public void warning(SAXParseException exception) throws SAXException {throw exception;} |
91 |
public void error(SAXParseException exception) throws SAXException {throw exception;} |
92 |
public void fatalError(SAXParseException exception) throws SAXException {throw exception;} |
93 |
}; |
94 |
|
95 |
private static final EntityResolver ENTITY_RESOLVER = new EntityResolver() { |
96 |
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { |
97 |
if (PUBLIC_DTD_ID.equals(publicId)) { |
98 |
return new InputSource(LayerGeneratingProcessor.class.getResource(LOCAL_DTD_RESOURCE).toString()); |
99 |
} else { |
100 |
return null; |
101 |
} |
102 |
} |
103 |
}; |
104 |
|
105 |
private static final Map<ProcessingEnvironment,Document> generatedLayerByProcessor = new WeakHashMap<ProcessingEnvironment,Document>(); |
106 |
private static final Map<ProcessingEnvironment,List<Element>> originatingElementsByProcessor = new WeakHashMap<ProcessingEnvironment,List<Element>>(); |
107 |
|
108 |
/** For access by subclasses. */ |
109 |
protected LayerGeneratingProcessor() {} |
110 |
|
111 |
@Override |
112 |
public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
113 |
boolean ret = doProcess(annotations, roundEnv); |
114 |
if (roundEnv.processingOver()) { |
115 |
Document doc = generatedLayerByProcessor.remove(processingEnv); |
116 |
if (doc != null) { |
117 |
Element[] originatingElementsA = new Element[0]; |
118 |
List<Element> originatingElementsL = originatingElementsByProcessor.remove(processingEnv); |
119 |
if (originatingElementsL != null) { |
120 |
originatingElementsA = originatingElementsL.toArray(originatingElementsA); |
121 |
} |
122 |
try { |
123 |
// Write to memory and reparse to make sure it is valid according to DTD before writing to disk. |
124 |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
125 |
XMLUtil.write(doc, baos, "UTF-8"); |
126 |
byte[] data = baos.toByteArray(); |
127 |
XMLUtil.parse(new InputSource(new ByteArrayInputStream(data)), true, true, ERROR_HANDLER, ENTITY_RESOLVER); |
128 |
FileObject layer = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", GENERATED_LAYER, originatingElementsA); |
129 |
OutputStream os = layer.openOutputStream(); |
130 |
try { |
131 |
os.write(data); |
132 |
} finally { |
133 |
os.close(); |
134 |
} |
135 |
{ |
136 |
SortedSet<String> files = new TreeSet<String>(); |
137 |
NodeList nl = doc.getElementsByTagName("file"); |
138 |
for (int i = 0; i < nl.getLength(); i++) { |
139 |
org.w3c.dom.Element e = (org.w3c.dom.Element) nl.item(i); |
140 |
String name = e.getAttribute("name"); |
141 |
while ((e = (org.w3c.dom.Element) e.getParentNode()).getTagName().equals("folder")) { |
142 |
name = e.getAttribute("name") + "/" + name; |
143 |
} |
144 |
files.add(name); |
145 |
} |
146 |
processingEnv.getMessager().printMessage(Kind.NOTE, "generated layer entries: " + files); |
147 |
} |
148 |
} catch (IOException x) { |
149 |
processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write generated-layer.xml: " + x.toString()); |
150 |
} catch (SAXException x) { |
151 |
processingEnv.getMessager().printMessage(Kind.ERROR, "Refused to write invalid generated-layer.xml: " + x.toString()); |
152 |
} |
153 |
} |
154 |
} |
155 |
return ret; |
156 |
} |
157 |
|
158 |
/** |
159 |
* The regular body of {@link #process}. |
160 |
* In the last round, one of the layer-generating processors will write out generated-layer.xml. |
161 |
* <p>Do not attempt to read or write the layer file directly; just use {@link #layer}. |
162 |
* You may however wish to create other resource files yourself: see {@link LayerBuilder.File#url} for syntax. |
163 |
* @param annotations as in {@link #process} |
164 |
* @param roundEnv as in {@link #process} |
165 |
* @return as in {@link #process} |
166 |
*/ |
167 |
protected abstract boolean doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv); |
168 |
|
169 |
/** |
170 |
* Access the generated XML layer document. |
171 |
* May already have content from a previous compilation run which should be overwritten. |
172 |
* May also have content from other layer-generated processors which should be appended to. |
173 |
* Simply make changes to the document and they will be written to disk at the end of the job. |
174 |
* <p>Use {@link LayerBuilder} to easily add file entries without working with the DOM directly. |
175 |
* @param originatingElements as in {@link Filer#createResource}, optional |
176 |
* @return the DOM document corresponding to the XML layer being created |
177 |
*/ |
178 |
protected final Document layer(Element... originatingElements) { |
179 |
List<Element> originatingElementsL = originatingElementsByProcessor.get(processingEnv); |
180 |
if (originatingElementsL == null) { |
181 |
originatingElementsL = new ArrayList<Element>(); |
182 |
originatingElementsByProcessor.put(processingEnv, originatingElementsL); |
183 |
} |
184 |
originatingElementsL.addAll(Arrays.asList(originatingElements)); |
185 |
Document doc = generatedLayerByProcessor.get(processingEnv); |
186 |
if (doc == null) { |
187 |
try { |
188 |
FileObject layer = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", GENERATED_LAYER); |
189 |
InputStream is = layer.openInputStream(); |
190 |
try { |
191 |
doc = XMLUtil.parse(new InputSource(is), true, true, ERROR_HANDLER, ENTITY_RESOLVER); |
192 |
} finally { |
193 |
is.close(); |
194 |
} |
195 |
} catch (FileNotFoundException fnfe) { |
196 |
// Fine, not yet created. |
197 |
} catch (IOException x) { |
198 |
processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to read generated-layer.xml: " + x.toString()); |
199 |
} catch (SAXException x) { |
200 |
processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to parse generated-layer.xml: " + x.toString()); |
201 |
} |
202 |
if (doc == null) { |
203 |
doc = XMLUtil.createDocument("filesystem", null, PUBLIC_DTD_ID, NETWORK_DTD_URL); |
204 |
} |
205 |
generatedLayerByProcessor.put(processingEnv, doc); |
206 |
} |
207 |
return doc; |
208 |
} |
209 |
|
210 |
/** |
211 |
* Generate an instance file whose {@code InstanceCookie} would load a given class or method. |
212 |
* Useful for processors which define layer fragments which instantiate Java objects from the annotated code. |
213 |
* <p>While you can pick a specific instance file name, if possible you should pass null for {@code name} |
214 |
* as using the generated name will help avoid accidental name collisions between annotations. |
215 |
* @param builder a builder to add a file to |
216 |
* @param annotationTarget an annotated {@linkplain TypeElement class} or {@linkplain ExecutableElement method} |
217 |
* @param path path to folder of instance file, e.g. {@code "Menu/File"} |
218 |
* @param name instance file basename, e.g. {@code "my-menu-Item"}, or null to pick a name according to the element |
219 |
* @param type a type to which the instance ought to be assignable, or null to skip this check |
220 |
* @return an instance file (call {@link LayerBuilder.File#write} to finalize) |
221 |
* @throws IllegalArgumentException if the annotationTarget is not of a suitable sort |
222 |
* (detail message can be reported as a {@link Kind#ERROR}) |
223 |
*/ |
224 |
protected final LayerBuilder.File instanceFile(LayerBuilder builder, Element annotationTarget, |
225 |
String path, String name, Class type) throws IllegalArgumentException { |
226 |
String clazz, method; |
227 |
TypeMirror typeMirror = type != null ? processingEnv.getElementUtils().getTypeElement(type.getName()).asType() : null; |
228 |
switch (annotationTarget.getKind()) { |
229 |
case CLASS: { |
230 |
clazz = processingEnv.getElementUtils().getBinaryName((TypeElement) annotationTarget).toString(); |
231 |
method = null; |
232 |
if (annotationTarget.getModifiers().contains(Modifier.ABSTRACT)) { |
233 |
throw new IllegalArgumentException(clazz + " must not be abstract"); |
234 |
} |
235 |
{ |
236 |
boolean hasDefaultCtor = false; |
237 |
for (ExecutableElement constructor : ElementFilter.constructorsIn(annotationTarget.getEnclosedElements())) { |
238 |
if (constructor.getParameters().isEmpty()) { |
239 |
hasDefaultCtor = true; |
240 |
break; |
241 |
} |
242 |
} |
243 |
if (!hasDefaultCtor) { |
244 |
throw new IllegalArgumentException(clazz + " must have a no-argument constructor"); |
245 |
} |
246 |
} |
247 |
if (typeMirror != null && !processingEnv.getTypeUtils().isAssignable(annotationTarget.asType(), typeMirror)) { |
248 |
throw new IllegalArgumentException(clazz + " is not assignable to " + typeMirror); |
249 |
} |
250 |
break; |
251 |
} |
252 |
case METHOD: { |
253 |
clazz = processingEnv.getElementUtils().getBinaryName((TypeElement) annotationTarget.getEnclosingElement()).toString(); |
254 |
method = annotationTarget.getSimpleName().toString(); |
255 |
if (!annotationTarget.getModifiers().contains(Modifier.STATIC)) { |
256 |
throw new IllegalArgumentException(clazz + "." + method + " must be static"); |
257 |
} |
258 |
if (!((ExecutableElement) annotationTarget).getParameters().isEmpty()) { |
259 |
throw new IllegalArgumentException(clazz + "." + method + " must not take arguments"); |
260 |
} |
261 |
if (typeMirror != null && !processingEnv.getTypeUtils().isAssignable(((ExecutableElement) annotationTarget).getReturnType(), typeMirror)) { |
262 |
throw new IllegalArgumentException(clazz + "." + method + " is not assignable to " + typeMirror); |
263 |
} |
264 |
break; |
265 |
} |
266 |
default: |
267 |
throw new IllegalArgumentException("Annotated element is not loadable as an instance: " + annotationTarget); |
268 |
} |
269 |
String basename; |
270 |
if (name == null) { |
271 |
basename = clazz.replace('.', '-'); |
272 |
if (method != null) { |
273 |
basename += "-" + method; |
274 |
} |
275 |
} else { |
276 |
basename = name; |
277 |
} |
278 |
LayerBuilder.File f = builder.file(path + "/" + basename + ".instance"); |
279 |
if (method != null) { |
280 |
f.methodvalue("instanceCreate", clazz, method); |
281 |
} else if (name != null) { |
282 |
f.stringvalue("instanceClass", clazz); |
283 |
} // else name alone suffices |
284 |
return f; |
285 |
} |
286 |
|
287 |
} |