001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.controls.runtime.generator;
020:
021: import java.util.ArrayList;
022: import java.util.HashSet;
023: import java.util.Collection;
024: import java.util.HashMap;
025: import java.util.List;
026: import java.io.IOException;
027: import java.io.Writer;
028:
029: import com.sun.mirror.apt.Filer;
030: import com.sun.mirror.declaration.*;
031: import com.sun.mirror.type.TypeMirror;
032: import com.sun.mirror.type.ClassType;
033:
034: import org.apache.beehive.controls.api.events.EventHandler;
035: import org.apache.beehive.controls.runtime.generator.apt.TwoPhaseAnnotationProcessor;
036:
037: /**
038: * The AptControlClient class contains metadata about a class that contains nested control
039: * references (AptControlField).
040: */
041: public class AptControlClient extends AptType implements Generator {
042: /**
043: * Constructs a new ControlClient instance where information is derived
044: * from APT metadata
045: * @param decl the annotated declaration
046: */
047: public AptControlClient(Declaration decl,
048: TwoPhaseAnnotationProcessor ap) {
049: _ap = ap;
050: if (!(decl instanceof ClassDeclaration)) {
051: _ap.printError(decl, "control.illegal.usage");
052: return;
053: }
054: _clientDecl = (ClassDeclaration) decl;
055: setDeclaration(_clientDecl);
056:
057: _controls = initControls();
058: initEventAdaptors();
059:
060: //
061: // Construct a new initializer class from this implementation class
062: //
063: _init = new ClientInitializer(this );
064: }
065:
066: /**
067: * Returns true if this type of client requires that nested controls have unique identifiers
068: */
069: protected boolean needsUniqueID() {
070: //
071: // BUGBUG:
072: // Pageflows need to have a more unique ID generated for fields, because multiple pageflows
073: // may be shared within a single ControlContainerContext, and just using the field name could
074: // result in collisions. A better (and less hard-wired) approach is needed than searching for
075: // specific annotations. Perhaps a model that enables particular client types to subclass
076: // AptControlClient and override getID() would be much better.
077: //
078: for (AnnotationMirror annotMirror : _clientDecl
079: .getAnnotationMirrors()) {
080: String annotType = annotMirror.getAnnotationType()
081: .toString();
082: if (annotType
083: .equals("org.apache.beehive.netui.pageflow.annotations.Jpf.Controller")
084: || annotType
085: .equals("org.apache.beehive.netui.pageflow.annotations.Jpf.Backing"))
086: return true;
087: }
088: return false;
089: }
090:
091: /**
092: * Returns a unique ID for a control field
093: */
094: public String getID(AptControlField control) {
095: if (!needsUniqueID())
096: return "\"" + control.getName() + "\"";
097:
098: return "client.getClass() + \"@\" + client.hashCode() + \"."
099: + control.getClassName() + "." + control.getName()
100: + "\"";
101: }
102:
103: /**
104: * Returns the list of ControlFields declared directly by this ControlImpl
105: */
106: public ArrayList<AptControlField> getControls() {
107: return _controls;
108: }
109:
110: /**
111: * Returns true if the implemenation class contains any nested controls
112: */
113: public boolean hasControls() {
114: return _controls.size() != 0;
115: }
116:
117: /**
118: * Returns true if the control client needs field initialization support
119: */
120: public boolean needsFieldInit() {
121: return hasControls();
122: }
123:
124: /**
125: * Returns the field with the specified name
126: */
127: public AptField getField(String name) {
128: for (AptField field : _controls)
129: if (field.getName().equals(name))
130: return field;
131:
132: return null;
133: }
134:
135: /**
136: * Returns the list of fully qualified class names for types that are derived
137: * from this Generator
138: */
139: public String[] getGeneratedTypes() {
140: return new String[] { _init.getClassName() };
141: }
142:
143: /**
144: * Returns the information necessary to generate a ImplInitializer from this
145: * ControlImplementation.
146: */
147: public List<GeneratorOutput> getCheckOutput(Filer filer)
148: throws IOException {
149: return null;
150: }
151:
152: /**
153: * Returns the information necessary to generate a ClientInitializer from this control
154: */
155: public List<GeneratorOutput> getGenerateOutput(Filer filer)
156: throws IOException {
157: HashMap<String, Object> map = new HashMap<String, Object>();
158: map.put("client", this ); // control client
159: map.put("init", _init); // control client initializer
160:
161: Writer writer = new IndentingWriter(filer
162: .createSourceFile(_init.getClassName()));
163: GeneratorOutput genOut = new GeneratorOutput(
164: writer,
165: "org/apache/beehive/controls/runtime/generator/ClientInitializer.vm",
166: map);
167: ArrayList<GeneratorOutput> genList = new ArrayList<GeneratorOutput>(
168: 1);
169: genList.add(genOut);
170: return genList;
171: }
172:
173: /**
174: * Initializes the list of ControlFields declared directly by this ControlClient
175: */
176: protected ArrayList<AptControlField> initControls() {
177: ArrayList<AptControlField> controls = new ArrayList<AptControlField>();
178:
179: if (_clientDecl == null || _clientDecl.getFields() == null)
180: return controls;
181:
182: Collection<FieldDeclaration> declaredFields = _clientDecl
183: .getFields();
184: for (FieldDeclaration fieldDecl : declaredFields) {
185: if (fieldDecl
186: .getAnnotation(org.apache.beehive.controls.api.bean.Control.class) != null)
187: controls.add(new AptControlField(this , fieldDecl, _ap));
188: }
189: return controls;
190: }
191:
192: public boolean hasSuperClient() {
193: return (getSuperClientName() != null);
194: }
195:
196: /**
197: * Returns the fully qualified classname of the closest control client in the inheritance chain.
198: * @return class name of the closest control client
199: */
200: public String getSuperClientName() {
201: ClassType super Type = _clientDecl.getSuperclass();
202:
203: while (super Type != null) {
204: ClassDeclaration super Decl = super Type.getDeclaration();
205:
206: Collection<FieldDeclaration> declaredFields = super Decl
207: .getFields();
208: for (FieldDeclaration fieldDecl : declaredFields) {
209: if (fieldDecl
210: .getAnnotation(org.apache.beehive.controls.api.bean.Control.class) != null) {
211: // Found an @control annotated field, so return this class name
212: return super Decl.getQualifiedName();
213: }
214: }
215:
216: super Type = super Type.getSuperclass();
217: }
218:
219: return null;
220: }
221:
222: /**
223: * Returns the super class for this class
224: */
225: public AptControlClient getSuperClass() {
226: return null;
227: }
228:
229: /**
230: * Initializes the list of EventAdaptors for this ControlImpl
231: */
232: protected void initEventAdaptors() {
233: if (_clientDecl == null || _clientDecl.getMethods() == null)
234: return;
235:
236: for (MethodDeclaration clientMethod : _clientDecl.getMethods()) {
237: //
238: // Do a quick check for the presence of the EventHandler annotation on methods
239: //
240: if (clientMethod.getAnnotation(EventHandler.class) == null
241: || clientMethod.toString().equals("<clinit>()"))
242: continue;
243:
244: //
245: // EventHandler annotations on private methods cause compilation error.
246: //
247: if (isPrivateMethod(clientMethod)) {
248: _ap.printError(clientMethod,
249: "eventhandler.method.is.private");
250: continue;
251: }
252:
253: //
254: // If found, we must actually read the value using an AnnotationMirror, since it
255: // contains a Class element (eventSet) that cannot be loaded
256: //
257: AnnotationMirror handlerMirror = null;
258: for (AnnotationMirror annot : clientMethod
259: .getAnnotationMirrors()) {
260: if (annot == null
261: || annot.getAnnotationType() == null
262: || annot.getAnnotationType().getDeclaration() == null
263: || annot.getAnnotationType().getDeclaration()
264: .getQualifiedName() == null)
265: return;
266:
267: if (annot
268: .getAnnotationType()
269: .getDeclaration()
270: .getQualifiedName()
271: .equals(
272: "org.apache.beehive.controls.api.events.EventHandler")) {
273: handlerMirror = annot;
274: break;
275: }
276: }
277: if (handlerMirror == null) {
278: throw new CodeGenerationException(
279: "Unable to find EventHandler annotation on "
280: + clientMethod);
281: }
282:
283: AptAnnotationHelper handlerAnnot = new AptAnnotationHelper(
284: handlerMirror);
285:
286: //
287: // Locate the EventField based upon the field element value
288: //
289: String fieldName = (String) handlerAnnot
290: .getObjectValue("field");
291: AptEventField eventField = (AptEventField) getField(fieldName);
292: if (eventField == null) {
293: // Deliberately not issuing a diagnostic if an event handler specifies
294: // a field that isn't a control. Other annotation processors also
295: // handle event handlers, so delegate diagnostic responsibility to them.
296: continue;
297: }
298:
299: //
300: // Locate the EventSet based upon the eventSet element value
301: //
302: Object tmo = handlerAnnot.getObjectValue("eventSet");
303: if (!(tmo instanceof TypeMirror))
304: continue;
305:
306: TypeMirror tm = (TypeMirror) tmo;
307: String setName = tm.toString();
308:
309: AptControlInterface controlIntf = eventField
310: .getControlInterface();
311: AptEventSet eventSet = controlIntf.getEventSet(setName);
312:
313: // todo: remove workaround once bug has been resolved.
314: /* Workaround JIRA issue BEEHIVE-1143, eventset name may
315: contain a '$' seperator between the outer class and inner class.
316: Should be a '.' seperator. Only applies to Eclipse APT. This
317: workaround is also present in AptControlImplementation.initEventAdapters
318: */
319: if (tm.getClass().getName().startsWith("org.eclipse.")) {
320: setName = setName.replace('$', '.');
321: }
322: // end of workaround
323:
324: if (eventSet == null) {
325: _ap.printError(clientMethod,
326: "eventhandler.eventset.not.found", setName);
327: continue;
328: }
329:
330: //
331: // Register a new EventAdaptor for the EventSet, if none exists already
332: //
333: EventAdaptor adaptor = eventField.getEventAdaptor(eventSet);
334: if (adaptor == null) {
335: adaptor = new EventAdaptor(eventField, eventSet);
336: eventField.addEventAdaptor(eventSet, adaptor);
337: }
338:
339: //
340: // Locate the EventSet method based upon the eventName element value. Once
341: // found, add a new AptEventHandler to the adaptor for this event.
342: //
343: boolean found = false;
344: String eventName = (String) handlerAnnot
345: .getObjectValue("eventName");
346: AptMethod handlerMethod = new AptMethod(clientMethod, _ap);
347:
348: //
349: // Will start at the currrent event set and look up through any ones it
350: // extends to try and find a matching event
351: //
352: while (eventSet != null) {
353: for (AptEvent controlEvent : eventSet.getEvents()) {
354: if (controlEvent == null
355: || controlEvent.getName() == null
356: || !controlEvent.getName()
357: .equals(eventName))
358: continue;
359:
360: if (controlEvent.getArgTypes() == null)
361: continue;
362:
363: //
364: // BUGBUG: If the arguments are parameterized, then the event handler
365: // might declare a specific bound version of the type, so a direct
366: // comparison will fail. If parameterized, we don't validate.
367: //
368: if (controlEvent.hasParameterizedArguments()
369: || (controlEvent.getArgTypes().equals(
370: handlerMethod.getArgTypes()) && controlEvent
371: .getReturnType().equals(
372: handlerMethod
373: .getReturnType()))) {
374: HashSet<String> throwSet = new HashSet<String>(
375: controlEvent.getThrowsList());
376: ArrayList<String> handlerThrows = handlerMethod
377: .getThrowsList();
378: boolean throwsMatches = true;
379: for (String t : handlerThrows) {
380: if (!throwSet.contains(t))
381: throwsMatches = false;
382: }
383:
384: if (!throwsMatches) {
385: _ap.printError(clientMethod,
386: "eventhandler.throws.mismatch",
387: handlerMethod.getName());
388: }
389:
390: adaptor.addHandler(controlEvent,
391: new AptEventHandler(controlEvent,
392: clientMethod, _ap));
393: found = true;
394: break;
395: }
396: }
397: if (found) // outer loop too
398: break;
399:
400: //
401: // Look up on the super event set if not found at the current level
402: //
403: eventSet = eventSet.getSuperEventSet();
404: }
405: if (!found) {
406: _ap.printError(clientMethod,
407: "eventhandler.method.not.found", setName);
408: }
409: }
410: }
411:
412: ClassDeclaration _clientDecl;
413: TwoPhaseAnnotationProcessor _ap;
414: ArrayList<AptControlField> _controls;
415: ClientInitializer _init;
416: }
|