/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
* Portions Copyright 2013-2017 Philip Helger + contributors
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.helger.jcodemodel;
import static com.helger.jcodemodel.util.JCEqualsHelper.isEqual;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.helger.jcodemodel.util.JCHashCodeGenerator;
/**
* {@link JMethod} invocation
*/
public class JInvocation extends AbstractJExpressionImpl implements IJStatement, IJOwnedMaybe
{
private final JCodeModel m_aOwner;
/**
* Object expression upon which this method will be invoked, or null if this
* is a constructor invocation
*/
private final IJGenerable m_aObject;
/**
* Name of the method to be invoked. Either this field is set, or
* {@link #m_sMethod}, or {@link #m_aConstructorType} (in which case it's a
* constructor invocation.) This allows {@link JMethod#name(String) the name
* of the method to be changed later}.
*/
private final String m_sMethodName;
private final JMethod m_sMethod;
private final boolean m_bIsConstructor;
/**
* List of argument expressions for this method invocation
*/
private final List <IJExpression> _args = new ArrayList <> ();
/**
* If isConstructor==true, this field keeps the type to be created.
*/
private final AbstractJType m_aConstructorType;
/**
* Lazily created list of {@link JTypeVar}s.
*/
private List <JTypeVar> _typeVariables;
/**
* Invokes a method on an object.
*
* @param object
* JExpression for the object upon which the named method will be
* invoked, or null if none
* @param name
* Name of method to invoke
*/
protected JInvocation (@Nullable final IJExpression object, @Nonnull final String name)
{
// Not possible to determine an owner :(
this (null, object, name);
}
protected JInvocation (@Nullable final IJExpression object, @Nonnull final JMethod method)
{
this (method.owner (), object, method);
}
/**
* Invokes a static method on a class.
*
* @param aType
* Parent type
* @param sMethodName
* Method name to be invoked
*/
protected JInvocation (@Nonnull final AbstractJClass aType, @Nonnull final String sMethodName)
{
this (aType.owner (), aType, sMethodName);
}
/**
* Invokes a static method on a class.
*
* @param aType
* Parent type
* @param aMethod
* Method to be invoked
*/
protected JInvocation (@Nonnull final AbstractJClass aType, @Nonnull final JMethod aMethod)
{
this (aType.owner (), aType, aMethod);
}
private JInvocation (@Nullable final JCodeModel owner,
@Nullable final IJGenerable object,
@Nonnull final String sName)
{
if (sName.indexOf ('.') >= 0)
throw new IllegalArgumentException ("method name contains '.': " + sName);
m_aOwner = owner;
m_aObject = object;
m_sMethodName = sName;
m_sMethod = null;
m_bIsConstructor = false;
m_aConstructorType = null;
}
private JInvocation (@Nonnull final JCodeModel owner,
@Nullable final IJGenerable object,
@Nonnull final JMethod method)
{
m_aOwner = owner;
m_aObject = object;
m_sMethodName = null;
m_sMethod = method;
m_bIsConstructor = false;
m_aConstructorType = null;
}
/**
* Invokes a constructor of an object (i.e., creates a new object.)
*
* @param aConstructorType
* Type of the object to be created. If this type is an array type,
* added arguments are treated as array initializer. Thus you can
* create an expression like <code>new int[]{1,2,3,4,5}</code>.
*/
protected JInvocation (@Nonnull final AbstractJType aConstructorType)
{
m_aOwner = aConstructorType.owner ();
m_aObject = null;
m_sMethodName = null;
m_sMethod = null;
m_bIsConstructor = true;
m_aConstructorType = aConstructorType;
}
@Nullable
public JCodeModel owner ()
{
return m_aOwner;
}
public boolean isConstructor ()
{
return m_bIsConstructor;
}
/**
* Add an expression to this invocation's argument list
*
* @param arg
* Argument to add to argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final IJExpression arg)
{
if (arg == null)
throw new IllegalArgumentException ("argument may not be null");
_args.add (arg);
return this;
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final boolean v)
{
return arg (JExpr.lit (v));
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final char v)
{
return arg (JExpr.lit (v));
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final double v)
{
return arg (JExpr.lit (v));
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final float v)
{
return arg (JExpr.lit (v));
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final int v)
{
return arg (JExpr.lit (v));
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final long v)
{
return arg (JExpr.lit (v));
}
/**
* Adds a literal argument. Short for {@code arg(JExpr.lit(v))}
*
* @param v
* Value to be added to the argument list
* @return this for chaining
*/
@Nonnull
public JInvocation arg (@Nonnull final String v)
{
return arg (JExpr.lit (v));
}
/**
* Returns all arguments of the invocation.
*
* @return If there's no arguments, an empty array will be returned.
*/
@Nonnull
public IJExpression [] listArgs ()
{
return _args.toArray (new IJExpression [_args.size ()]);
}
/**
* Returns all arguments of the invocation.
*
* @return If there's no arguments, an empty list will be returned.
*/
@Nonnull
public List <IJExpression> args ()
{
return new ArrayList <> (_args);
}
@Nonnull
private JCodeModel _narrowOwner ()
{
final JCodeModel owner = owner ();
if (owner == null)
throw new IllegalStateException ("No owner is present, so this invocation cannot be generified!");
return owner;
}
@Nonnull
public JInvocation narrow (@Nonnull final String name)
{
final JTypeVar v = new JTypeVar (_narrowOwner (), name);
if (_typeVariables == null)
_typeVariables = new ArrayList <> (3);
_typeVariables.add (v);
return this;
}
@Nonnull
public JInvocation narrow (@Nonnull final Class <?> bound)
{
return narrow (_narrowOwner ().ref (bound));
}
@Nonnull
public JInvocation narrow (@Nonnull final AbstractJClass bound)
{
final JTypeVar v = new JTypeVarClass (bound);
if (_typeVariables == null)
_typeVariables = new ArrayList <> (3);
_typeVariables.add (v);
return this;
}
@Nonnull
public List <JTypeVar> typeParamList ()
{
if (_typeVariables == null)
return Collections.<JTypeVar> emptyList ();
return new ArrayList <> (_typeVariables);
}
private void _addTypeVars (@Nonnull final JFormatter f)
{
if (_typeVariables != null && !_typeVariables.isEmpty ())
{
f.print ('<');
int nIndex = 0;
for (final JTypeVar aTypeVar : _typeVariables)
{
if (nIndex++ > 0)
f.print (',');
// Use type here to get the import (if needed)
f.type (aTypeVar);
}
f.print (JFormatter.CLOSE_TYPE_ARGS);
}
}
private String methodName ()
{
return m_sMethodName != null ? m_sMethodName : m_sMethod.name ();
}
public void generate (@Nonnull final JFormatter f)
{
if (m_bIsConstructor)
{
if (m_aConstructorType.isArray ())
{
// [RESULT] new T[]{arg1,arg2,arg3,...};
f.print ("new").generable (m_aConstructorType);
_addTypeVars (f);
f.print ('{');
}
else
{
// [RESULT] new T(
f.print ("new").generable (m_aConstructorType);
_addTypeVars (f);
f.print ('(');
}
}
else
{
final String name = methodName ();
if (m_aObject != null)
{
// object.<generics> name (
f.generable (m_aObject).print ('.');
_addTypeVars (f);
f.print (name).print ('(');
}
else
{
// name (
f.id (name).print ('(');
}
}
// Method arguments
f.generable (_args);
// Close arg list
if (m_bIsConstructor && m_aConstructorType.isArray ())
f.print ('}');
else
f.print (')');
if (m_aConstructorType instanceof JDefinedClass && ((JDefinedClass) m_aConstructorType).isAnonymous ())
{
((JAnonymousClass) m_aConstructorType).declareBody (f);
}
}
public void state (@Nonnull final JFormatter f)
{
f.generable (this).print (';').newline ();
}
private String typeFullName ()
{
return m_aConstructorType != null ? m_aConstructorType.fullName () : "";
}
@Override
public boolean equals (final Object o)
{
if (o == this)
return true;
if (o == null || getClass () != o.getClass ())
return false;
final JInvocation rhs = (JInvocation) o;
if (!(isEqual (m_aObject, rhs.m_aObject) &&
isEqual (m_bIsConstructor, rhs.m_bIsConstructor) &&
(m_bIsConstructor || isEqual (methodName (), rhs.methodName ())) &&
isEqual (_args, rhs._args) &&
isEqual (typeFullName (), rhs.typeFullName ())))
{
return false;
}
if (_typeVariables == null)
return rhs._typeVariables == null;
if (rhs._typeVariables == null)
return false;
if (_typeVariables.size () != rhs._typeVariables.size ())
return false;
for (int i = 0; i < _typeVariables.size (); i++)
{
if (!isEqual (_typeVariables.get (i).fullName (), rhs._typeVariables.get (i).fullName ()))
return false;
}
return true;
}
@Override
public int hashCode ()
{
JCHashCodeGenerator hashCodeGenerator = new JCHashCodeGenerator (this).append (m_aObject).append (m_bIsConstructor);
if (!m_bIsConstructor)
hashCodeGenerator = hashCodeGenerator.append (methodName ());
hashCodeGenerator = hashCodeGenerator.append (_args).append (typeFullName ());
if (_typeVariables != null)
{
hashCodeGenerator = hashCodeGenerator.append (_typeVariables.size ());
for (final JTypeVar typeVariable : _typeVariables)
{
hashCodeGenerator = hashCodeGenerator.append (typeVariable.fullName ());
}
}
return hashCodeGenerator.getHashCode ();
}
}