All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.presents.tools.InvocationTask Maven / Gradle / Ivy

The newest version!
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.tools;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

import java.io.File;
import java.util.Arrays;
import java.util.List;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import com.samskivert.util.Logger;
import com.samskivert.util.StringUtil;

import com.threerings.presents.annotation.TransportHint;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService.InvocationListener;
import com.threerings.presents.net.Transport;

/**
 * A base Ant task for generating invocation service related marshalling and unmarshalling
 * classes.
 */
public abstract class InvocationTask extends GenTask
{
    /** Used to keep track of invocation service method listener arguments. */
    public class ListenerArgument
    {
        public Class listener;

        public ListenerArgument (int index, Class listener) {
            this.listener = listener;
            _index = index;
        }

        public String getMarshaller () {
            String name = GenUtil.simpleName(listener);
            // handle ye olde special case
            if (name.equals("InvocationService.InvocationListener")) {
                return "ListenerMarshaller";
            }
            name = name.replace("Service", "Marshaller");
            return name.replace("Listener", "Marshaller");
        }

        public String getActionScriptMarshaller () {
            // handle ye olde special case
            String name = listener.getName();
            if (name.endsWith("InvocationService$InvocationListener")) {
                return "InvocationMarshaller_ListenerMarshaller";
            } else {
                return getMarshaller().replace('.', '_');
            }
        }

        public int getIndex () {
            return _index+1;
        }

        protected int _index;
    }

    /**
     * Creates a new service method and adds its basic imports to a set.
     * @param method the method to create
     * @param imports will be filled with the types required by the method
     */
    public ServiceMethod createAndGatherImports (Method method, ImportSet imports)
    {
        ServiceMethod sm = new ServiceMethod(method);
        sm.gatherImports(imports);
        return sm;
    }

    /** Used to keep track of invocation service methods or listener methods. */
    public class ServiceMethod implements Comparable
    {
        public Method method;
        public List listenerArgs = Lists.newArrayList();

        /**
         * Creates a new service method.
         * @param method the method to inspect
         */
        public ServiceMethod (Method method) {
            this.method = method;

            // if this method has listener arguments, we need to add listener argument info for them
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                Class arg = args[ii];
                while (arg.isArray()) {
                    arg = arg.getComponentType();
                }

                if (_ilistener.isAssignableFrom(arg)) {
                    listenerArgs.add(new ListenerArgument(ii, arg));
                }
            }
        }

        public void gatherImports (ImportSet imports) {
            // we need to look through our arguments and note any needed imports in the supplied
            // table
            for (Type type : method.getGenericParameterTypes()) {
                addImportsForType(type, imports);
            }

            // import Transport if used
            if (!StringUtil.isBlank(getTransport())) {
                imports.add(Transport.class);
            }
        }

        public String getCode () {
            return StringUtil.unStudlyName(method.getName()).toUpperCase();
        }

        public String getSenderMethodName () {
            String mname = method.getName();
            if (mname.startsWith("received")) {
                return "send" + mname.substring("received".length());
            } else {
                return mname;
            }
        }

        public String typeParams () {
            List params = Lists.newArrayList();
            for (Type type : method.getGenericParameterTypes()) {
                collectTypeParams(type, params);
            }
            // the trailing space in '> ' is needed
            return params.isEmpty() ? "" : StringUtil.toString(params, "<", "> ");
        }

        public String getArgList () {
            StringBuilder buf = new StringBuilder();
            Type[] ptypes = method.getGenericParameterTypes();
            for (int ii = 0; ii < ptypes.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                String simpleName = GenUtil.simpleName(ptypes[ii]);
                if (method.isVarArgs() && ii == ptypes.length - 1) {
                    // Switch [] with ... for varargs
                    buf.append(simpleName.substring(0, simpleName.length() - 2)).append("...");
                } else {
                    buf.append(simpleName);
                }
                buf.append(" arg").append(ii+1);
            }
            return buf.toString();
        }

        public String getASArgList () {
            StringBuilder buf = new StringBuilder();
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                buf.append("arg").append(ii+1).append(" :");
                buf.append(GenUtil.simpleASName(args[ii]));
            }
            return buf.toString();
        }

         public String getASInvokeArgList () {
            StringBuilder buf = new StringBuilder();
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                buf.append("arg").append(ii+1);
            }
            return buf.toString();
        }

        public String getWrappedArgList () {
            StringBuilder buf = new StringBuilder();
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                buf.append(boxArgument(args[ii], ii+1));
            }
            return buf.toString();
        }

        public void gatherASWrappedArgListImports (ImportSet set) {
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                set.addAll(GenUtil.getASBoxImports(args[ii]));
            }
        }

        public String getASWrappedArgList () {
            StringBuilder buf = new StringBuilder();
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                String index = String.valueOf(ii+1);
                String arg;
                if (_ilistener.isAssignableFrom(args[ii])) {
                    arg = GenUtil.boxASArgument(args[ii],  "listener" + index);
                } else {
                    arg = GenUtil.boxASArgument(args[ii],  "arg" + index);
                }
                buf.append(arg);
            }
            return buf.toString();
        }

        public boolean hasArgs () {
            return method.getParameterTypes().length > 0;
        }

        public boolean hasParameterizedArgs () {
            return Iterables.any(
                Arrays.asList(method.getGenericParameterTypes()), new Predicate() {
                public boolean apply (Type type) {
                    // TODO: might eventually need to handle generic arrays and wildcard types
                    return (type instanceof ParameterizedType);
                }
            });
        }

        public String getUnwrappedArgListAsListeners () {
            return getUnwrappedArgList(true);
        }

        public String getUnwrappedArgList () {
            return getUnwrappedArgList(false);
        }

        public String getUnwrappedArgList (boolean listenerMode) {
            StringBuilder buf = new StringBuilder();
            Type[] ptypes = method.getGenericParameterTypes();
            for (int ii = 0; ii < ptypes.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                buf.append(unboxArgument(ptypes[ii], ii, listenerMode));
            }
            return buf.toString();
        }

        public String getASUnwrappedArgListAsListeners () {
            return getASUnwrappedArgList(true);
        }

        public String getASUnwrappedArgList () {
            return getASUnwrappedArgList(false);
        }

        public String getASUnwrappedArgList (boolean listenerMode) {
            StringBuilder buf = new StringBuilder();
            Class[] args = method.getParameterTypes();
            for (int ii = 0; ii < args.length; ii++) {
                if (buf.length() > 0) {
                    buf.append(", ");
                }
                String arg;
                int argidx = ii;
                if (listenerMode && _ilistener.isAssignableFrom(args[ii])) {
                    arg = "listener" + argidx;
                } else {
                    arg = GenUtil.unboxASArgument(args[ii], "args[" + argidx + "]");
                }
                buf.append(arg);
            }
            return buf.toString();
        }

        public String getTransport () {
            TransportHint hint = method.getAnnotation(TransportHint.class);
            if (hint == null) {
                // inherit hint from interface annotation
                hint = method.getDeclaringClass().getAnnotation(TransportHint.class);
            }
            if (hint == null) {
                return "";
            }
            return ", Transport.getInstance(Transport.Type." +
                hint.type().name() + ", " + hint.channel() + ")";
        }

        // from interface Comparator
        public int compareTo (ServiceMethod other) {
            return getCode().compareTo(other.getCode());
        }

        @Override // from Object
        public boolean equals (Object other) {
            return (other instanceof ServiceMethod) && compareTo((ServiceMethod)other) == 0;
        }

        @Override // from Object
        public int hashCode () {
            return getCode().hashCode();
        }

        protected void addImportsForType (Type type, ImportSet imports) {
            if (type instanceof Class) {
                imports.add((Class)type);
            } else if (type instanceof ParameterizedType) {
                imports.add((Class)((ParameterizedType)type).getRawType());
                for (Type param : ((ParameterizedType)type).getActualTypeArguments()) {
                    addImportsForType(param, imports);
                }
            } else if (type instanceof WildcardType) {
                for (Type upper : ((WildcardType)type).getUpperBounds()) {
                    addImportsForType(upper, imports);
                }
                for (Type lower : ((WildcardType)type).getLowerBounds()) {
                    addImportsForType(lower, imports);
                }
            } else if (type instanceof GenericArrayType) {
                addImportsForType(((GenericArrayType)type).getGenericComponentType(), imports);
            } else if (type instanceof TypeVariable) {
                // nothing needed
            } else {
                System.err.println(Logger.format(
                    "Unhandled Type in adding imports for a service", "type", type, "typeClass",
                    type.getClass()));
            }
        }

        protected void collectTypeParams (Type type, List params) {
            if (type instanceof TypeVariable) {
                String tvar = ((TypeVariable)type).getName();
                if (!params.contains(tvar)) params.add(tvar);
            } else if (type instanceof ParameterizedType) {
                for (Type pt : ((ParameterizedType)type).getActualTypeArguments()) {
                    collectTypeParams(pt, params);
                }
            } else if (type instanceof WildcardType) {
                for (Type lb : ((WildcardType)type).getLowerBounds()) {
                    collectTypeParams(lb, params);
                }
                for (Type ub : ((WildcardType)type).getUpperBounds()) {
                    collectTypeParams(ub, params);
                }
            } // else nada
        }

        protected String boxArgument (Class clazz, int index) {
            if (_ilistener.isAssignableFrom(clazz)) {
                return GenUtil.boxArgument(clazz,  "listener" + index);
            } else {
                return GenUtil.boxArgument(clazz,  "arg" + index);
            }
        }

        protected String unboxArgument (Type type, int index, boolean listenerMode) {
            if (listenerMode && (type instanceof Class) &&
                _ilistener.isAssignableFrom((Class)type)) {
                return "listener" + index;
            } else {
                return GenUtil.unboxArgument(type, "args[" + index + "]");
            }
        }
    }

    @Override
    public void execute ()
    {
        // resolve the InvocationListener and Client classes using our classloader
        _ilistener = loadClass(InvocationListener.class.getName());
        _iclient = loadClass(Client.class.getName());

        super.execute();
    }

    protected static  void checkedAdd (List list, T value)
    {
        if (!list.contains(value)) {
            list.add(value);
        }
    }

    protected static String replacePath (String source, String oldstr, String newstr)
    {
        // replace only the last occurrance of 'oldstr' with 'newstr'; if we have a path like:
        // /client/src/foo/bar/client/FooService.java we don't want to replace the first /client
        // just the one that immediately precedes /FooService.java

        // TODO: this is error prone because we could have a project that opted not to have
        // client/data/server subdirs and instead just had a path like:
        // /client/src/foo/bar/FooService.java which would be improperly mangled

        String oldPath = oldstr.replace('/', File.separatorChar);
        String newPath = newstr.replace('/', File.separatorChar);
        int oldIdx = source.lastIndexOf(oldPath);
        return (oldIdx == -1) ? source :
            (source.substring(0, oldIdx) + newPath + source.substring(oldIdx+oldPath.length()));
    }

    /** {@link InvocationListener} resolved with the proper classloader so that we can compare it
     * to loaded derived classes. */
    protected Class _ilistener;

    /** {@link Client} resolved with the proper classloader so that we can compare it to loaded
     * derived classes. */
    protected Class _iclient;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy