/* * The contents of this file are subject to the terms of the Common Development * and Distribution License (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at http://www.netbeans.org/cddl.html * or http://www.netbeans.org/cddl.txt. * * When distributing Covered Code, include this CDDL Header Notice in each file * and include the License file at http://www.netbeans.org/cddl.txt. * If applicable, add the following below the CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Portions Copyrighted 2007 Sun Microsystems, Inc. */ package org.netbeans.modules.gwt4nb.hints; import com.sun.source.tree.BlockTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ModifiersTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.netbeans.api.java.source.CancellableTask; import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.JavaSource.Phase; import org.netbeans.api.java.source.TreeMaker; import org.netbeans.api.java.source.WorkingCopy; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.modules.gwt4nb.GWTProjectInfo; import org.netbeans.modules.gwt4nb.Version; import org.netbeans.modules.gwt4nb.services.ServiceClassSetUtils; import org.netbeans.spi.editor.hints.ChangeInfo; import org.netbeans.spi.editor.hints.Fix; import org.openide.filesystems.FileObject; import org.openide.util.NbBundle; /** * * @author prem */ final class FixAsyncInterfaceImpl implements Fix { private static final Version V15 = new Version("1.5"); // NOI18N private FileObject asyncFo; private FileObject syncFo; private Version gwtVersion; private ExecutableElement serviceMethod; @SuppressWarnings("rawtypes") private List<ElementHandle> methods; private ElementHandle serviceMethodHandle; private List<ElementHandle> unmatchedMethods; @SuppressWarnings("rawtypes") public FixAsyncInterfaceImpl( final FileObject asyncFo, final FileObject syncFo, final List<ElementHandle> methods, final List<ElementHandle> unmatchedMethods) { this.asyncFo = asyncFo; this.syncFo = syncFo; this.methods = methods; this.unmatchedMethods = unmatchedMethods; final Project owner = FileOwnerQuery.getOwner(asyncFo); this.gwtVersion = GWTProjectInfo.get( owner).getGWTVersion(); } protected Set<Modifier> getMethodModifiers( final ExecutableElement serviceMethod) { final Set<Modifier> modifiers = EnumSet.copyOf(serviceMethod. getModifiers()); modifiers.remove(Modifier.ABSTRACT); return modifiers; } private Tree getReturnTypeTree(final TreeMaker make, final TypeMirror type) { final TypeKind kind = type.getKind(); if (kind.isPrimitive()) { return make.Identifier(JavaModelUtils.getNonPrimitiveTypeName( type, false)); } else if (kind == TypeKind.VOID) { return make.Identifier("Void"); // NOI18N } else { return make.Type(type); } } protected Tree getAsyncCallback(final TreeMaker make, final ExecutableElement serviceMethod) { final TypeMirror type = serviceMethod.getReturnType(); if (gwtVersion.compareTo(V15) < 0) { return make.Identifier("AsyncCallback"); // NOI18N } else { return make.ParameterizedType( make.Identifier("AsyncCallback"), // NOI18N Collections.singletonList(getReturnTypeTree(make, type))); } } protected MethodTree createMethodTree(final TreeMaker make) { final ModifiersTree emptyModifiers = make.Modifiers( Collections.<Modifier>emptySet()); final List<? extends VariableElement> serviceParameters = serviceMethod. getParameters(); final List<VariableTree> parameters = new ArrayList<VariableTree>(serviceParameters.size() + 1); for (final VariableElement var : serviceParameters) { final TypeMirror mirror = var.asType(); final CharSequence typeName = mirror.getKind() == TypeKind.DECLARED ? ((DeclaredType) mirror).asElement().getSimpleName() : var.asType().toString(); Tree paramType; if (mirror.getKind() == TypeKind.DECLARED) { List<Tree> argTrees = new ArrayList<Tree>(); List<? extends TypeMirror> typeArguments = ((DeclaredType) mirror).getTypeArguments(); for (final TypeMirror m : typeArguments) { argTrees.add(make.Type(m)); } if (argTrees.isEmpty()) { paramType = make.Identifier(typeName); } else { paramType = make.ParameterizedType( make.Identifier(typeName), argTrees); } } else { paramType = make.Identifier(typeName); } parameters.add(make.Variable( emptyModifiers, var.getSimpleName(), paramType, null)); } parameters.add(make.Variable( emptyModifiers, "asyncCallback", // NOI18N getAsyncCallback(make, serviceMethod), null)); final MethodTree newMethod = make.Method( make.Modifiers(getMethodModifiers(serviceMethod)), serviceMethod.getSimpleName(), make.Identifier("void"), // NOI18N Collections.<TypeParameterTree>emptyList(), parameters, Collections.<ExpressionTree>emptyList(), (BlockTree) null, null); return newMethod; } @Override public String getText() { return NbBundle.getMessage(FixAsyncInterfaceImpl.class, "HINT_SYNC"); // NOI18N } @Override public ChangeInfo implement() { JavaSource jsSync = JavaSource.forFileObject(syncFo); JavaSource jsAsync = JavaSource.forFileObject(asyncFo); try { jsSync.runModificationTask(new CancellableTask<WorkingCopy>() { @Override public void cancel() { } @Override public void run(WorkingCopy workingCopy) { // dummy task to change the state of classB } }).commit(); jsAsync.runModificationTask(new RemoveUnmatchedMethods()).commit(); for (ElementHandle eh : methods) { serviceMethodHandle = eh; jsAsync.runModificationTask(new CreateAsyncMethod()).commit(); } } catch (IOException e) { e.printStackTrace(); } return null; } private class CreateAsyncMethod implements CancellableTask<WorkingCopy> { private boolean isAsyncClassName(final Name name) { return name.toString().endsWith(ServiceClassSetUtils.ASYNC_SUFFIX); } private void resolveServiceMethod(final WorkingCopy workingCopy) { serviceMethod = (ExecutableElement) serviceMethodHandle.resolve( workingCopy); } private void createAsyncMethodImpl( final TreeMaker make, final ClassTree clazz, final WorkingCopy workingCopy) { final MethodTree newMethod = createMethodTree(make); final ClassTree modifiedClazz = make.addClassMember(clazz, newMethod); workingCopy.rewrite(clazz, modifiedClazz); } @Override public void cancel() { } @Override public void run(final WorkingCopy workingCopy) throws IOException { workingCopy.toPhase(Phase.RESOLVED); CompilationUnitTree cut = workingCopy.getCompilationUnit(); TreeMaker make = workingCopy.getTreeMaker(); for (final Tree typeDecl : cut.getTypeDecls()) { if (Tree.Kind.INTERFACE == typeDecl.getKind()) { final ClassTree clazz = (ClassTree) typeDecl; if (isAsyncClassName(clazz.getSimpleName())) { resolveServiceMethod(workingCopy); // what happens if the service-method has been deleted // or edited? it won't resolve if it has, so we do // this check to make sure it's all still the same if (serviceMethod != null) { createAsyncMethodImpl(make, clazz, workingCopy); } } } } workingCopy.toPhase(Phase.UP_TO_DATE); } } private class RemoveUnmatchedMethods implements CancellableTask<WorkingCopy> { @Override public void cancel() { } @Override public void run(final WorkingCopy workingCopy) throws IOException { workingCopy.toPhase(Phase.RESOLVED); CompilationUnitTree cut = workingCopy.getCompilationUnit(); TreeMaker make = workingCopy.getTreeMaker(); for (Tree typeDecl : cut.getTypeDecls()) { if (Tree.Kind.INTERFACE == typeDecl.getKind()) { ClassTree clazz = (ClassTree) typeDecl; ClassTree modifiedClazz = clazz; if (clazz.getSimpleName().toString().endsWith(ServiceClassSetUtils.ASYNC_SUFFIX)) { for (ElementHandle classAMethodHandle : unmatchedMethods) { ExecutableElement classAMethod = (ExecutableElement) classAMethodHandle.resolve(workingCopy); MethodTree aMethod = workingCopy.getTrees().getTree(classAMethod); modifiedClazz = make.removeClassMember(modifiedClazz, aMethod); } if (modifiedClazz != null) { workingCopy.rewrite(clazz, modifiedClazz); } } } } workingCopy.toPhase(Phase.UP_TO_DATE); } } }