/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.codegen.util;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import org.jboss.errai.codegen.DefParameters;
import org.jboss.errai.codegen.Modifier;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.StringStatement;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.MethodBlockBuilder;
import org.jboss.errai.codegen.builder.MethodCommentBuilder;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaConstructor;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import com.google.gwt.core.client.UnsafeNativeLong;
/**
* @author Mike Brock
*/
public class GWTPrivateMemberAccessor implements PrivateMemberAccessor {
/**
* Annotation instance that can be passed to the code generator when generating long accessors.
*/
private static final UnsafeNativeLong UNSAFE_NATIVE_LONG_ANNOTATION = new UnsafeNativeLong() {
@Override
public Class<? extends Annotation> annotationType() {
return UnsafeNativeLong.class;
}
};
/**
* A reusable empty annotation array.
*/
private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];
@Override
public void createWritableField(final MetaClass type,
final ClassStructureBuilder<?> classBuilder,
final MetaField field,
final Modifier[] modifiers) {
final MethodCommentBuilder<? extends ClassStructureBuilder<?>> methodBuilder =
classBuilder.packageMethod(void.class, PrivateAccessUtil.getPrivateFieldAccessorName(field));
if (type.getCanonicalName().equals("long")) {
methodBuilder.annotatedWith(UNSAFE_NATIVE_LONG_ANNOTATION);
}
if (!field.isStatic()) {
methodBuilder
.parameters(DefParameters.fromParameters(Parameter.of(field.getDeclaringClass().getErased(), "instance"),
Parameter.of(type, "value")));
} else {
methodBuilder
.parameters(DefParameters.fromParameters(Parameter.of(type, "value")));
}
methodBuilder.modifiers(appendJsni(modifiers))
.body()
._(StringStatement.of(JSNIUtil.fieldAccess(field) + " = value"))
.finish();
}
@Override
public void createReadableField(final MetaClass type,
final ClassStructureBuilder<?> classBuilder,
final MetaField field,
final Modifier[] modifiers) {
final MethodBlockBuilder<? extends ClassStructureBuilder<?>> instance =
classBuilder.packageMethod(type, PrivateAccessUtil.getPrivateFieldAccessorName(field));
if (!field.isStatic()) {
instance.parameters(DefParameters.fromParameters(Parameter.of(field.getDeclaringClass().getErased(), "instance")));
}
if (type.getCanonicalName().equals("long")) {
instance.annotatedWith(UNSAFE_NATIVE_LONG_ANNOTATION);
}
instance.modifiers(appendJsni(modifiers))
.body()
._(StringStatement.of("return " + JSNIUtil.fieldAccess(field)))
.finish();
}
@Override
public void makeMethodAccessible(final ClassStructureBuilder<?> classBuilder,
final MetaMethod method,
final Modifier[] modifiers) {
final MetaMethod erasedMethod = method.getDeclaringClass().getErased().getDeclaredMethod(method.getName(),
getErasedParamterTypes(method));
final List<Parameter> wrapperDefParms = new ArrayList<Parameter>();
if (!erasedMethod.isStatic()) {
wrapperDefParms.add(Parameter.of(erasedMethod.getDeclaringClass().getErased(), "instance"));
}
final List<Parameter> methodDefParms = DefParameters.from(erasedMethod).getParameters();
wrapperDefParms.addAll(methodDefParms);
Annotation[] annotations = NO_ANNOTATIONS;
for (MetaParameter p : erasedMethod.getParameters()) {
if (p.getType().getCanonicalName().equals("long")) {
annotations = new Annotation[] { UNSAFE_NATIVE_LONG_ANNOTATION };
}
}
if (erasedMethod.getReturnType().getCanonicalName().equals("long")) {
annotations = new Annotation[] { UNSAFE_NATIVE_LONG_ANNOTATION };
}
classBuilder.publicMethod(erasedMethod.getReturnType(), PrivateAccessUtil.getPrivateMethodName(method))
.annotatedWith(annotations)
.parameters(DefParameters.fromParameters(wrapperDefParms))
.modifiers(appendJsni(modifiers))
.body()
._(StringStatement.of(JSNIUtil.methodAccess(erasedMethod)))
.finish();
}
private MetaClass[] getErasedParamterTypes(final MetaMethod method) {
final MetaClass[] paramTypes = new MetaClass[method.getParameters().length];
for (int i = 0; i < paramTypes.length; i++) {
paramTypes[i] = method.getParameters()[i].getType().getErased();
}
return paramTypes;
}
@Override
public void makeConstructorAccessible(final ClassStructureBuilder<?> classBuilder,
final MetaConstructor constructor) {
final DefParameters methodDefParms = DefParameters.from(constructor);
Annotation[] annotations = NO_ANNOTATIONS;
for (MetaParameter p : constructor.getParameters()) {
if (p.getType().getCanonicalName().equals("long")) {
annotations = new Annotation[] { UNSAFE_NATIVE_LONG_ANNOTATION };
}
}
classBuilder.publicMethod(constructor.getReturnType(), PrivateAccessUtil.getPrivateMethodName(constructor))
.annotatedWith(annotations)
.parameters(methodDefParms)
.modifiers(Modifier.Static, Modifier.JSNI)
.body()
._(StringStatement.of(JSNIUtil.methodAccess(constructor)))
.finish();
}
/**
* Returns a new array consisting of a copy of the given array, plus
* Modifiers.JSNI as the last element.
*
* @param modifiers
* The array to copy. May be empty, but must not be null.
*
* @return An array of length {@code n + 1}, where {@code n} is the length of
* the given array. Positions 0..n-1 correspond with the respective
* entries in the given array, and position n contains Modifiers.JSNI.
*/
public static Modifier[] appendJsni(Modifier[] modifiers) {
final Modifier[] origModifiers = modifiers;
modifiers = new Modifier[origModifiers.length + 1];
System.arraycopy(origModifiers, 0, modifiers, 0, origModifiers.length);
modifiers[modifiers.length - 1] = Modifier.JSNI;
return modifiers;
}
}