/*
* Copyright 2013 eXo Platform SAS
*
* Licensed 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.
*/
package juzu.impl.plugin.controller.metamodel;
import juzu.Mapped;
import juzu.Param;
import juzu.impl.compiler.ProcessingContext;
import juzu.impl.plugin.module.metamodel.ModuleMetaModel;
import juzu.impl.metamodel.AnnotationKey;
import juzu.impl.metamodel.AnnotationState;
import juzu.impl.compiler.ElementHandle;
import juzu.impl.compiler.MessageCode;
import juzu.impl.metamodel.Key;
import juzu.impl.metamodel.MetaModelEvent;
import juzu.impl.metamodel.MetaModelObject;
import juzu.impl.common.Cardinality;
import juzu.impl.common.JSON;
import juzu.request.Phase;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public class ControllerMetaModel extends MetaModelObject implements Iterable<HandlerMetaModel> {
/** . */
public static final MessageCode CANNOT_WRITE_CONTROLLER_COMPANION = new MessageCode("CANNOT_WRITE_CONTROLLER_COMPANION", "The controller companion %1$s cannot be written");
/** . */
public static final MessageCode CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED = new MessageCode("CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED", "The method parameter type %1s should be a string or annotated with @juzu.Param");
/** . */
public static final MessageCode CONTROLLER_IS_ABSTRACT = new MessageCode("CONTROLLER_IS_ABSTRACT", "The controller class %1s cannot be abstract");
/** A flag for handling modified event. */
boolean modified;
/** The application. */
ControllersMetaModel controllers;
/** . */
final ElementHandle.Type handle;
public ControllerMetaModel(ElementHandle.Type handle) {
this.handle = handle;
this.modified = false;
}
public Iterator<HandlerMetaModel> iterator() {
return getHandlers().iterator();
}
public JSON toJSON() {
JSON json = new JSON();
json.set("handle", handle);
json.map("methods", getHandlers());
return json;
}
public ControllersMetaModel getControllers() {
return controllers;
}
public ElementHandle.Type getHandle() {
return handle;
}
public Collection<HandlerMetaModel> getHandlers() {
return getChildren(HandlerMetaModel.class);
}
private PhaseParameterMetaModel foo(
VariableElement parameterVariableElt,
String parameterName,
Cardinality parameterCardinality,
String type,
String valueType) {
// Not sure we should use @Param for this (i.e for now it looks hackish)
// however it does make sense later to use the regex part for non router
// parameters
Param param = parameterVariableElt.getAnnotation(Param.class);
String alias = param != null && param.name().length() > 0 ? param.name() : null;
return new PhaseParameterMetaModel(parameterName, parameterCardinality, type, valueType, alias);
}
private ParameterMetaModel foo(ModuleMetaModel context, VariableElement parameterVariableElt, TypeMirror parameterTypeMirror) {
String type = context.processingContext.getLiteralName(parameterTypeMirror);
//
String parameterName = parameterVariableElt.getSimpleName().toString();
//
if (parameterVariableElt.getAnnotation(Mapped.class) != null) {
return new BeanParameterMetaModel(parameterName, type);
} else {
// Determine cardinality
TypeMirror parameterValueTypeMirror;
Cardinality parameterCardinality;
switch (parameterTypeMirror.getKind()) {
case INT:
return foo(parameterVariableElt, parameterName, Cardinality.SINGLE, "int", "int");
case DECLARED:
DeclaredType dt = (DeclaredType)parameterTypeMirror;
TypeElement col = context.processingContext.getTypeElement("java.util.List");
TypeMirror tm = context.processingContext.erasure(col.asType());
TypeMirror err = context.processingContext.erasure(dt);
if (err.equals(tm)) {
if (dt.getTypeArguments().size() != 1) {
throw CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED.failure(parameterVariableElt);
} else {
parameterCardinality = Cardinality.LIST;
parameterValueTypeMirror = dt.getTypeArguments().get(0);
if (parameterValueTypeMirror.getKind() != TypeKind.DECLARED) {
throw CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED.failure(parameterVariableElt);
}
}
} else {
parameterCardinality = Cardinality.SINGLE;
parameterValueTypeMirror = parameterTypeMirror;
}
break;
case ARRAY:
// Unwrap array
ArrayType arrayType = (ArrayType)parameterTypeMirror;
parameterCardinality = Cardinality.ARRAY;
parameterValueTypeMirror = arrayType.getComponentType();
switch (parameterValueTypeMirror.getKind()) {
case DECLARED:
break;
case INT:
return foo(parameterVariableElt, parameterName, Cardinality.ARRAY, "int[]", "int");
default:
throw CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED.failure(parameterVariableElt);
}
break;
default:
throw CONTROLLER_METHOD_PARAMETER_NOT_RESOLVED.failure(parameterVariableElt);
}
//
TypeElement valueType = (TypeElement)context.processingContext.asElement(parameterValueTypeMirror);
ElementHandle.Type valueTypeHandle = ElementHandle.Type.create(valueType);
//
if (valueType.toString().equals("java.lang.String") || controllers.plugin.valueTypes.contains(valueTypeHandle)) {
return foo(parameterVariableElt, parameterName, parameterCardinality, type, valueType.toString());
} else {
return new ContextualParameterMetaModel(parameterName, type);
}
}
}
void addMethod(ModuleMetaModel context, AnnotationKey annotationKey, AnnotationState annotationState) {
//
String id = (String)annotationState.get("id");
ElementHandle.Method methodHandle = (ElementHandle.Method)annotationKey.getElement();
ExecutableElement methodElt = context.processingContext.get(methodHandle);
ProcessingContext.log.log(Level.FINE, "Adding method " + methodElt + " to controller class " + handle);
//
for (Phase phase : Phase.values()) {
if (phase.annotation.getName().equals(annotationKey.getType().toString())) {
// First remove the previous method
Key<HandlerMetaModel> key = Key.of(methodHandle, HandlerMetaModel.class);
if (getChild(key) == null) {
// Parameters
ArrayList<ParameterMetaModel> parameters = new ArrayList<ParameterMetaModel>();
List<? extends TypeMirror> parameterTypeMirrors = ((ExecutableType)methodElt.asType()).getParameterTypes();
List<? extends VariableElement> parameterVariableElements = methodElt.getParameters();
for (int i = 0;i < parameterTypeMirrors.size();i++) {
VariableElement parameterVariableElt = parameterVariableElements.get(i);
TypeMirror parameterTypeMirror = parameterTypeMirrors.get(i);
ParameterMetaModel parameterMetaModel = foo(context, parameterVariableElt, parameterTypeMirror);
parameters.add(parameterMetaModel);
}
//
HandlerMetaModel method = new HandlerMetaModel(
methodHandle,
id,
phase,
methodElt.getSimpleName().toString(),
parameters);
addChild(key, method);
modified = true;
ProcessingContext.log.log(Level.FINE, "Added method " + methodHandle + " to controller class " + handle);
}
break;
}
}
}
void removeMethod(ElementHandle.Method handle) {
ProcessingContext.log.log(Level.FINE, "Removing method " + handle + " from controller class " + handle);
if (removeChild(Key.of(handle, HandlerMetaModel.class)) != null) {
modified = true;
ProcessingContext.log.log(Level.FINE, "Removed method " + handle + " from controller class " + handle);
}
}
@Override
protected void preDetach(MetaModelObject parent) {
if (parent instanceof ControllersMetaModel) {
queue(MetaModelEvent.createRemoved(this));
controllers = null;
}
}
@Override
protected void postAttach(MetaModelObject parent) {
if (parent instanceof ControllersMetaModel) {
controllers = (ControllersMetaModel)parent;
queue(MetaModelEvent.createAdded(this));
}
}
}