
com.phloc.html.js.builder.JSDefinedClass Maven / Gradle / Ivy
/**
* Copyright (C) 2006-2015 phloc systems
* http://www.phloc.com
* office[at]phloc[dot]com
*
* 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 com.phloc.html.js.builder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.CodingStyleguideUnaware;
import com.phloc.commons.annotations.Nonempty;
import com.phloc.commons.annotations.ReturnsMutableCopy;
import com.phloc.commons.collections.ContainerHelper;
import com.phloc.commons.equals.EqualsUtils;
import com.phloc.commons.hash.HashCodeGenerator;
import com.phloc.commons.string.ToStringGenerator;
import com.phloc.html.js.marshal.JSMarshaller;
/**
* A generated JS class.
*
* @author Philip Helger
*/
public class JSDefinedClass extends AbstractJSClass implements IJSDeclaration, IJSDocCommentable
{
private static final Logger s_aLogger = LoggerFactory.getLogger (JSDefinedClass.class);
/** class JSDoc */
private JSCommentMultiLine m_aJSDoc;
/** Name of this class. */
private final String m_sName;
/** Name of the super class of this class. */
private AbstractJSClass m_aSuperClass;
/** Fields keyed by their names. */
private final Map m_aFields = new LinkedHashMap ();
/** Constructors for this class */
private JSConstructor m_aConstructor;
/** Set of methods that are members of this class */
private final List m_aMethods = new ArrayList ();
/**
* constructor
*
* @param sName
* Name of this class
*/
public JSDefinedClass (@Nonnull @Nonempty final String sName)
{
if (!JSMarshaller.isJSIdentifier (sName))
throw new IllegalArgumentException ("The name '" + sName + "' is not a legal JS identifier!");
if (!Character.isUpperCase (sName.charAt (0)))
s_aLogger.warn ("Class names should always start with an upper-case character: " + sName);
m_sName = sName;
}
/**
* This class extends the specified class.
*
* @param aSuperClass
* Superclass for this class
* @return This class
*/
@Nonnull
@CodingStyleguideUnaware
public JSDefinedClass _extends (@Nonnull final AbstractJSClass aSuperClass)
{
m_aSuperClass = ValueEnforcer.notNull (aSuperClass, "SuperClass");
return this;
}
/**
* Returns the class extended by this class.
*/
@Override
@Nullable
@CodingStyleguideUnaware
public AbstractJSClass _extends ()
{
return m_aSuperClass;
}
/**
* JClass name accessor.
*
* For example, for java.util.List
, this method returns
* "List"
"
*
* @return Name of this class
*/
@Override
@Nonnull
@Nonempty
public String name ()
{
return m_sName;
}
/**
* Adds a field to the list of field members of this defined class.
*
* @param sName
* Name of this field
* @return Newly generated field
*/
@Nonnull
public JSFieldVar field (@Nonnull @Nonempty final String sName)
{
return field (null, sName, null);
}
/**
* Adds a field to the list of field members of this defined class.
*
* @param aType
* type of this field
* @param sName
* Name of this field
* @return Newly generated field
*/
@Nonnull
public JSFieldVar field (@Nullable final AbstractJSType aType, @Nonnull @Nonempty final String sName)
{
return field (aType, sName, null);
}
/**
* Adds a field to the list of field members of this defined class.
*
* @param sName
* Name of this field.
* @param aInit
* Initial value of this field.
* @return Newly generated field
*/
@Nonnull
public JSFieldVar field (@Nonnull @Nonempty final String sName, @Nullable final IJSExpression aInit)
{
return field (null, sName, aInit);
}
/**
* Adds a field to the list of field members of this defined class.
*
* @param aType
* type of this field.
* @param sName
* Name of this field.
* @param aInit
* Initial value of this field.
* @return Newly generated field
*/
@Nonnull
public JSFieldVar field (@Nullable final AbstractJSType aType,
@Nonnull @Nonempty final String sName,
@Nullable final IJSExpression aInit)
{
final JSFieldVar aField = new JSFieldVar (this, aType, sName, aInit);
return addField (aField);
}
@Nullable
public JSFieldVar getFieldOfName (@Nullable final String sName)
{
return m_aFields.get (sName);
}
@Nonnull
public JSFieldVar addField (@Nonnull final JSFieldVar aField) throws JSNameAlreadyExistsException
{
ValueEnforcer.notNull (aField, "Field");
final String sName = aField.name ();
final JSFieldVar aOldField = getFieldOfName (sName);
if (aOldField != null)
throw new JSNameAlreadyExistsException (aOldField);
m_aFields.put (sName, aField);
return aField;
}
/**
* Returns all the fields declared in this class. The returned {@link Map} is
* a read-only live view.
*
* @return always non-null.
*/
@Nonnull
@ReturnsMutableCopy
public Map fields ()
{
return ContainerHelper.newMap (m_aFields);
}
public boolean containsField (@Nullable final String sName)
{
return m_aFields.containsKey (sName);
}
/**
* Removes a {@link JSFieldVar} from this class.
*
* @return this
* @throws IllegalArgumentException
* if the given field is not a field on this class.
*/
@Nonnull
public JSDefinedClass removeField (@Nonnull final JSFieldVar aField)
{
ValueEnforcer.notNull (aField, "Field");
if (m_aFields.remove (aField.name ()) != aField)
throw new IllegalArgumentException ("Failed to remove field '" + aField.name () + "' from " + m_aFields.keySet ());
return this;
}
@Nonnull
public JSDefinedClass removeAllFields ()
{
m_aFields.clear ();
return this;
}
/**
* Adds a constructor to this class.
*
* @return The constructor object to use. Never null
.
*/
@Nonnull
public JSConstructor constructor ()
{
if (m_aConstructor == null)
m_aConstructor = new JSConstructor (this);
return m_aConstructor;
}
/**
* Add a method to the list of method members of this JS class instance.
*
* @param sName
* Name of the method
* @return Newly generated method
*/
@Nonnull
public JSMethod method (@Nonnull @Nonempty final String sName)
{
return method (null, sName);
}
/**
* Add a method to the list of method members of this JS class instance.
*
* @param aType
* Return type for this method
* @param sName
* Name of the method
* @return Newly generated method
*/
@Nonnull
public JSMethod method (@Nullable final AbstractJSType aType, @Nonnull @Nonempty final String sName)
{
final JSMethod aMethod = new JSMethod (this, aType, sName);
m_aMethods.add (aMethod);
return aMethod;
}
/**
* Returns the set of methods defined in this class.
*/
@Nonnull
@ReturnsMutableCopy
public List methods ()
{
return ContainerHelper.newList (m_aMethods);
}
/**
* Creates, if necessary, and returns the class JSDoc for this defined class
*
* @return {@link JSCommentMultiLine} containing JSDoc for this class
*/
@Nonnull
public JSCommentMultiLine jsDoc ()
{
if (m_aJSDoc == null)
m_aJSDoc = new JSCommentMultiLine ();
return m_aJSDoc;
}
@Nonnull
public JSFieldRef prototype ()
{
return staticRef ("prototype");
}
public void declare (@Nonnull final JSFormatter aFormatter)
{
if (m_aJSDoc != null)
aFormatter.nl ().generatable (m_aJSDoc);
// Emit the constructor first (a function)
aFormatter.decl (constructor ());
final JSAssocArray aPrototypefields = new JSAssocArray ();
// Add all fields
for (final JSFieldVar aField : m_aFields.values ())
aPrototypefields.add (aField.name (), aField.hasInit () ? aField.init () : JSExpr.NULL);
// Add all methods
for (final JSMethod aMethod : m_aMethods)
aPrototypefields.add (aMethod.name (), aMethod.getAsAnonymousFunction ());
// Start with the prototype methods
JSExpr.assign (prototype (), aPrototypefields).generate (aFormatter);
}
@Nullable
public String getJSCode ()
{
return JSPrinter.getAsString ((IJSDeclaration) this);
}
@Override
public boolean equals (final Object o)
{
if (o == this)
return true;
if (!super.equals (o))
return false;
final JSDefinedClass rhs = (JSDefinedClass) o;
return EqualsUtils.equals (m_aJSDoc, rhs.m_aJSDoc) &&
m_sName.equals (rhs.m_sName) &&
EqualsUtils.equals (m_aSuperClass, rhs.m_aSuperClass) &&
m_aFields.equals (rhs.m_aFields) &&
EqualsUtils.equals (m_aConstructor, rhs.m_aConstructor) &&
m_aMethods.equals (rhs.m_aMethods);
}
@Override
public int hashCode ()
{
return HashCodeGenerator.getDerived (super.hashCode ())
.append (m_aJSDoc)
.append (m_sName)
.append (m_aSuperClass)
.append (m_aFields)
.append (m_aConstructor)
.append (m_aMethods)
.getHashCode ();
}
@Override
public String toString ()
{
return ToStringGenerator.getDerived (super.toString ())
.appendIfNotNull ("jsDoc", m_aJSDoc)
.append ("name", m_sName)
.appendIfNotNull ("superClass", m_aSuperClass)
.append ("fields", m_aFields)
.appendIfNotNull ("constructor", m_aConstructor)
.append ("methods", m_aMethods)
.toString ();
}
}