org.apache.beehive.controls.runtime.generator.AptControlClient Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Header:$
*/
package org.apache.beehive.controls.runtime.generator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.io.IOException;
import java.io.Writer;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.TypeMirror;
import com.sun.mirror.type.ClassType;
import org.apache.beehive.controls.api.events.EventHandler;
import org.apache.beehive.controls.runtime.generator.apt.TwoPhaseAnnotationProcessor;
/**
* The AptControlClient class contains metadata about a class that contains nested control
* references (AptControlField).
*/
public class AptControlClient extends AptType implements Generator
{
/**
* Constructs a new ControlClient instance where information is derived
* from APT metadata
* @param decl the annotated declaration
*/
public AptControlClient(Declaration decl, TwoPhaseAnnotationProcessor ap)
{
_ap = ap;
if (! (decl instanceof ClassDeclaration))
{
_ap.printError( decl, "control.illegal.usage" );
return;
}
_clientDecl = (ClassDeclaration)decl;
setDeclaration(_clientDecl);
_controls = initControls();
initEventAdaptors();
//
// Construct a new initializer class from this implementation class
//
_init = new ClientInitializer(this);
}
/**
* Returns true if this type of client requires that nested controls have unique identifiers
*/
protected boolean needsUniqueID()
{
//
// BUGBUG:
// Pageflows need to have a more unique ID generated for fields, because multiple pageflows
// may be shared within a single ControlContainerContext, and just using the field name could
// result in collisions. A better (and less hard-wired) approach is needed than searching for
// specific annotations. Perhaps a model that enables particular client types to subclass
// AptControlClient and override getID() would be much better.
//
for (AnnotationMirror annotMirror : _clientDecl.getAnnotationMirrors())
{
String annotType = annotMirror.getAnnotationType().toString();
if (annotType.equals("org.apache.beehive.netui.pageflow.annotations.Jpf.Controller") ||
annotType.equals("org.apache.beehive.netui.pageflow.annotations.Jpf.Backing"))
return true;
}
return false;
}
/**
* Returns a unique ID for a control field
*/
public String getID(AptControlField control)
{
if (!needsUniqueID())
return "\"" + control.getName() + "\"";
return "client.getClass() + \"@\" + client.hashCode() + \"." + control.getClassName() + "." + control.getName() + "\"";
}
/**
* Returns the list of ControlFields declared directly by this ControlImpl
*/
public ArrayList getControls() { return _controls; }
/**
* Returns true if the implemenation class contains any nested controls
*/
public boolean hasControls() { return _controls.size() != 0; }
/**
* Returns true if the control client needs field initialization support
*/
public boolean needsFieldInit()
{
return hasControls();
}
/**
* Returns the field with the specified name
*/
public AptField getField(String name)
{
for (AptField field : _controls)
if (field.getName().equals(name))
return field;
return null;
}
/**
* Returns the list of fully qualified class names for types that are derived
* from this Generator
*/
public String [] getGeneratedTypes()
{
return new String [] { _init.getClassName() };
}
/**
* Returns the information necessary to generate a ImplInitializer from this
* ControlImplementation.
*/
public List getCheckOutput(Filer filer) throws IOException
{
return null;
}
/**
* Returns the information necessary to generate a ClientInitializer from this control
*/
public List getGenerateOutput(Filer filer) throws IOException
{
HashMap map = new HashMap();
map.put("client", this); // control client
map.put("init", _init); // control client initializer
Writer writer = new IndentingWriter(filer.createSourceFile(_init.getClassName()));
GeneratorOutput genOut =
new GeneratorOutput(writer,"org/apache/beehive/controls/runtime/generator/ClientInitializer.vm",
map);
ArrayList genList = new ArrayList(1);
genList.add(genOut);
return genList;
}
/**
* Initializes the list of ControlFields declared directly by this ControlClient
*/
protected ArrayList initControls()
{
ArrayList controls = new ArrayList();
if ( _clientDecl == null || _clientDecl.getFields() == null )
return controls;
Collection declaredFields = _clientDecl.getFields();
for (FieldDeclaration fieldDecl : declaredFields)
{
if (fieldDecl.getAnnotation(org.apache.beehive.controls.api.bean.Control.class) != null)
controls.add(new AptControlField(this, fieldDecl, _ap));
}
return controls;
}
public boolean hasSuperClient()
{
return ( getSuperClientName() != null );
}
/**
* Returns the fully qualified classname of the closest control client in the inheritance chain.
* @return class name of the closest control client
*/
public String getSuperClientName()
{
ClassType superType = _clientDecl.getSuperclass();
while ( superType != null )
{
ClassDeclaration superDecl = superType.getDeclaration();
Collection declaredFields = superDecl.getFields();
for (FieldDeclaration fieldDecl : declaredFields)
{
if (fieldDecl.getAnnotation(org.apache.beehive.controls.api.bean.Control.class) != null)
{
// Found an @control annotated field, so return this class name
return superDecl.getQualifiedName();
}
}
superType = superType.getSuperclass();
}
return null;
}
/**
* Returns the super class for this class
*/
public AptControlClient getSuperClass() { return null; }
/**
* Initializes the list of EventAdaptors for this ControlImpl
*/
protected void initEventAdaptors()
{
if ( _clientDecl == null || _clientDecl.getMethods() == null )
return;
for (MethodDeclaration clientMethod : _clientDecl.getMethods())
{
//
// Do a quick check for the presence of the EventHandler annotation on methods
//
if (clientMethod.getAnnotation(EventHandler.class) == null ||
clientMethod.toString().equals("()"))
continue;
//
// EventHandler annotations on private methods cause compilation error.
//
if (isPrivateMethod(clientMethod))
{
_ap.printError( clientMethod, "eventhandler.method.is.private");
continue;
}
//
// If found, we must actually read the value using an AnnotationMirror, since it
// contains a Class element (eventSet) that cannot be loaded
//
AnnotationMirror handlerMirror = null;
for (AnnotationMirror annot : clientMethod.getAnnotationMirrors())
{
if ( annot == null ||
annot.getAnnotationType() == null ||
annot.getAnnotationType().getDeclaration() == null ||
annot.getAnnotationType().getDeclaration().getQualifiedName() == null )
return;
if ( annot.getAnnotationType().getDeclaration().getQualifiedName().equals(
"org.apache.beehive.controls.api.events.EventHandler"))
{
handlerMirror = annot;
break;
}
}
if (handlerMirror == null)
{
throw new CodeGenerationException("Unable to find EventHandler annotation on " +
clientMethod);
}
AptAnnotationHelper handlerAnnot = new AptAnnotationHelper(handlerMirror);
//
// Locate the EventField based upon the field element value
//
String fieldName = (String)handlerAnnot.getObjectValue("field");
AptEventField eventField = (AptEventField)getField(fieldName);
if (eventField == null)
{
// Deliberately not issuing a diagnostic if an event handler specifies
// a field that isn't a control. Other annotation processors also
// handle event handlers, so delegate diagnostic responsibility to them.
continue;
}
//
// Locate the EventSet based upon the eventSet element value
//
Object tmo = handlerAnnot.getObjectValue("eventSet");
if (!(tmo instanceof TypeMirror))
continue;
TypeMirror tm = (TypeMirror)tmo;
String setName = tm.toString();
AptControlInterface controlIntf = eventField.getControlInterface();
AptEventSet eventSet = controlIntf.getEventSet(setName);
// todo: remove workaround once bug has been resolved.
/* Workaround JIRA issue BEEHIVE-1143, eventset name may
contain a '$' seperator between the outer class and inner class.
Should be a '.' seperator. Only applies to Eclipse APT. This
workaround is also present in AptControlImplementation.initEventAdapters
*/
if (tm.getClass().getName().startsWith("org.eclipse.")) {
setName = setName.replace('$', '.');
}
// end of workaround
if (eventSet == null)
{
_ap.printError( clientMethod, "eventhandler.eventset.not.found", setName );
continue;
}
//
// Register a new EventAdaptor for the EventSet, if none exists already
//
EventAdaptor adaptor = eventField.getEventAdaptor(eventSet);
if (adaptor == null)
{
adaptor = new EventAdaptor(eventField, eventSet);
eventField.addEventAdaptor(eventSet, adaptor);
}
//
// Locate the EventSet method based upon the eventName element value. Once
// found, add a new AptEventHandler to the adaptor for this event.
//
boolean found = false;
String eventName = (String)handlerAnnot.getObjectValue("eventName");
AptMethod handlerMethod = new AptMethod(clientMethod, _ap);
//
// Will start at the currrent event set and look up through any ones it
// extends to try and find a matching event
//
while (eventSet != null)
{
for (AptEvent controlEvent : eventSet.getEvents())
{
if (controlEvent == null ||
controlEvent.getName() == null ||
!controlEvent.getName().equals(eventName))
continue;
if ( controlEvent.getArgTypes() == null )
continue;
//
// BUGBUG: If the arguments are parameterized, then the event handler
// might declare a specific bound version of the type, so a direct
// comparison will fail. If parameterized, we don't validate.
//
if (controlEvent.hasParameterizedArguments() ||
(controlEvent.getArgTypes().equals(handlerMethod.getArgTypes()) &&
controlEvent.getReturnType().equals(handlerMethod.getReturnType())
)
)
{
HashSet throwSet = new HashSet(controlEvent.getThrowsList());
ArrayList handlerThrows = handlerMethod.getThrowsList();
boolean throwsMatches = true;
for ( String t : handlerThrows )
{
if ( !throwSet.contains(t) )
throwsMatches = false;
}
if ( !throwsMatches )
{
_ap.printError( clientMethod, "eventhandler.throws.mismatch", handlerMethod.getName() );
}
adaptor.addHandler(controlEvent,
new AptEventHandler(controlEvent, clientMethod, _ap ));
found = true;
break;
}
}
if (found) // outer loop too
break;
//
// Look up on the super event set if not found at the current level
//
eventSet = eventSet.getSuperEventSet();
}
if (!found)
{
_ap.printError( clientMethod, "eventhandler.method.not.found", setName );
}
}
}
ClassDeclaration _clientDecl;
TwoPhaseAnnotationProcessor _ap;
ArrayList _controls;
ClientInitializer _init;
}