/** * 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 java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.helger.jcodemodel.util.ClassNameComparator; /** * A generated Java class/interface/enum/.... * <p> * This class models a declaration, and since a declaration can be always used * as a reference, it inherits {@link AbstractJClass}. * <h2>Where to go from here?</h2> * <p> * You'd want to generate fields and methods on a class. See * {@link #method(int, AbstractJType, String)} and * {@link #field(int, AbstractJType, String)}. */ public class JDefinedClass extends AbstractJClassContainer <JDefinedClass> implements IJDeclaration, IJGenerifiable, IJAnnotatable, IJDocCommentable { /** * The optional header that is emitted prior to the package (Issue #47) */ private JDocComment m_aHeaderComment; /** * Modifiers for the class declaration */ private JMods m_aMods; /** * Name of the super class of this class. */ private AbstractJClass m_aSuperClass; /** * List of interfaces that this class implements */ private final Set <AbstractJClass> m_aInterfaces = new TreeSet <> (ClassNameComparator.getInstance ()); /** * Fields keyed by their names. */ /* package */final Map <String, JFieldVar> m_aFields = new LinkedHashMap <> (); /** * Static initializer, if this class has one */ private JBlock m_aStaticInit; /** * Instance initializer, if this class has one */ private JBlock m_aInstanceInit; /** * class javadoc */ private JDocComment m_aJDoc; /** * Set of constructors for this class, if any */ private final List <JMethod> m_aConstructors = new ArrayList <> (); /** * Set of methods that are members of this class */ private final List <JMethod> m_aMethods = new ArrayList <> (); /** * Flag that controls whether this class should be really generated or not. * Sometimes it is useful to generate code that refers to class X, without * actually generating the code of X. This flag is used to suppress X.java * file in the output. */ private boolean m_bHideFile = false; /** * Client-app specific metadata associated with this user-created class. */ @Deprecated public Object metadata; /** * String that will be put directly inside the generated code. Can be null. */ private String m_sDirectBlock; /** * List containing the enum value declarations */ // private List enumValues = new ArrayList(); /** * Set of enum constants that are keyed by names. In Java, enum constant order * is actually significant, because of order ID they get. So let's preserve * the order. */ private final Map <String, JEnumConstant> m_aEnumConstantsByName = new LinkedHashMap <> (); /** * Annotations on this variable. Lazily created. */ private List <JAnnotationUse> m_aAnnotations; /** * Helper class to implement {@link IJGenerifiable}. */ private final AbstractJGenerifiableImpl m_aGenerifiable = new AbstractJGenerifiableImpl () { @Nonnull public JCodeModel owner () { // The owner is same as the owner of this defined class's owner return JDefinedClass.this.owner (); } }; protected JDefinedClass (@Nonnull final IJClassContainer <?> aParent, final int nMods, @Nullable final String sName, @Nonnull final EClassType eClassType) { this (aParent.owner (), aParent, nMods, eClassType, sName); } /** * Constructor for creating anonymous inner class. * * @param aOwner * Owning code model * @param nMods * Java modifier * @param sName * Name of this class */ protected JDefinedClass (@Nonnull final JCodeModel aOwner, final int nMods, @Nullable final String sName) { this (aOwner, null, nMods, EClassType.CLASS, sName); } /** * JClass constructor * * @param aOwner * Owning code model * @param aOuter * Outer class or package * @param nMods * Modifiers for this class declaration * @param eClassType * Class type to use * @param sName * Name of this class */ private JDefinedClass (@Nonnull final JCodeModel aOwner, @Nullable final IJClassContainer <?> aOuter, final int nMods, @Nonnull final EClassType eClassType, @Nullable final String sName) { super (aOwner, aOuter, eClassType, sName); if (sName != null) { if (sName.trim ().length () == 0) throw new IllegalArgumentException ("JDefinedClass name empty"); if (!Character.isJavaIdentifierStart (sName.charAt (0))) { final String msg = "JDefinedClass name " + sName + " contains illegal character" + " for beginning of identifier: " + sName.charAt (0); throw new IllegalArgumentException (msg); } for (int i = 1; i < sName.length (); i++) { final char c = sName.charAt (i); if (!Character.isJavaIdentifierPart (c)) { final String msg = "JDefinedClass name " + sName + " contains illegal character " + c; throw new IllegalArgumentException (msg); } } } if (isInterface ()) m_aMods = JMods.forInterface (nMods); else m_aMods = JMods.forClass (nMods); } /** * @return the current modifiers of this class. Always return non-null valid * object. */ @Nonnull public JMods mods () { return m_aMods; } /** * This class extends the specified class. * * @param aSuperClass * Superclass for this class * @return This class */ @Nonnull public JDefinedClass _extends (@Nonnull final AbstractJClass aSuperClass) { if (isInterface ()) { if (aSuperClass.isInterface ()) return _implements (aSuperClass); throw new IllegalArgumentException ("unable to set the super class for an interface"); } if (aSuperClass == null) throw new NullPointerException (); for (AbstractJClass o = aSuperClass.outer (); o != null; o = o.outer ()) { if (this == o) { throw new IllegalArgumentException ("Illegal class inheritance loop." + " Outer class " + name () + " may not subclass from inner class: " + o.name ()); } } m_aSuperClass = aSuperClass; return this; } @Nonnull public JDefinedClass _extends (@Nonnull final Class <?> superClass) { return _extends (owner ().ref (superClass)); } /** * Returns the class extended by this class. */ @Override @Nonnull public AbstractJClass _extends () { if (m_aSuperClass == null) m_aSuperClass = owner ().ref (Object.class); return m_aSuperClass; } /** * This class implements the specifed interface. * * @param iface * Interface that this class implements * @return This class */ @Nonnull public JDefinedClass _implements (@Nonnull final AbstractJClass iface) { m_aInterfaces.add (iface); return this; } @Nonnull public JDefinedClass _implements (@Nonnull final Class <?> iface) { return _implements (owner ().ref (iface)); } /** * Returns an iterator that walks the nested classes defined in this class. */ @Override @Nonnull public Iterator <AbstractJClass> _implements () { return m_aInterfaces.iterator (); } /** * If the named enum already exists, the reference to it is returned. * Otherwise this method generates a new enum reference with the given name * and returns it. * * @param name * The name of the constant. * @return The generated type-safe enum constant. */ @Nonnull public JEnumConstant enumConstant (@Nonnull final String name) { JEnumConstant ec = m_aEnumConstantsByName.get (name); if (null == ec) { ec = new JEnumConstant (this, name); m_aEnumConstantsByName.put (name, ec); } return ec; } @Override public String binaryName () { if (getOuter () instanceof AbstractJClassContainer <?>) return ((AbstractJClassContainer <?>) getOuter ()).binaryName () + '$' + name (); // FIXME This is incorrect, e.g. for anonymous classes! return fullName (); } @Override public boolean isAbstract () { return m_aMods.isAbstract (); } /** * Adds a field to the list of field members of this JDefinedClass. * * @param mods * Modifiers for this field * @param type * JType of this field * @param name * Name of this field * @return Newly generated field */ public JFieldVar field (final int mods, final AbstractJType type, final String name) { return field (mods, type, name, null); } public JFieldVar field (final int mods, final Class <?> type, final String name) { return field (mods, owner ()._ref (type), name); } /** * Adds a field to the list of field members of this JDefinedClass. * * @param mods * Modifiers for this field. * @param type * JType of this field. * @param name * Name of this field. * @param init * Initial value of this field. * @return Newly generated field */ @Nonnull public JFieldVar field (final int mods, @Nonnull final AbstractJType type, @Nonnull final String name, @Nullable final IJExpression init) { final JFieldVar f = new JFieldVar (this, JMods.forField (mods), type, name, init); if (m_aFields.containsKey (name)) throw new IllegalArgumentException ("trying to create the same field twice: " + name); m_aFields.put (name, f); return f; } @Nonnull public JFieldVar field (final int mods, final Class <?> type, final String name, final IJExpression init) { return field (mods, owner ()._ref (type), name, init); } /** * Returns all the fields declared in this class. The returned {@link Map} is * a read-only live view. * * @return always non-null. */ @Nonnull public Map <String, JFieldVar> fields () { return Collections.unmodifiableMap (m_aFields); } /** * Removes a {@link JFieldVar} from this class. * * @param aField * Field to be removed * @throws IllegalArgumentException * if the given field is not a field on this class. */ public void removeField (@Nonnull final JFieldVar aField) { if (m_aFields.remove (aField.name ()) != aField) throw new IllegalArgumentException (); } /** * @param sName * Field name to check. May be <code>null</code>. * @return <code>true</code> if such a field is contained, <code>false</code> * otherwise. */ public boolean containsField (@Nullable final String sName) { return sName != null && m_aFields.containsKey (sName); } /** * Creates, if necessary, and returns the static initializer for this class. * * @return JBlock containing initialization statements for this class */ @Nonnull public JBlock init () { if (m_aStaticInit == null) m_aStaticInit = new JBlock (); return m_aStaticInit; } /** * Creates, if necessary, and returns the instance initializer for this class. * * @return JBlock containing initialization statements for this class */ @Nonnull public JBlock instanceInit () { if (m_aInstanceInit == null) m_aInstanceInit = new JBlock (); return m_aInstanceInit; } /** * Adds a constructor to this class. * * @param mods * Modifiers for this constructor * @return Newly created {@link JMethod} */ @Nonnull public JMethod constructor (final int mods) { final JMethod c = new JMethod (mods, this); m_aConstructors.add (c); return c; } /** * @return an iterator that walks the constructors defined in this class. */ @Nonnull public Iterator <JMethod> constructors () { return m_aConstructors.iterator (); } /** * Looks for a method that has the specified method signature and return it. * * @param aArgTypes * Signature to search * @return <code>null</code> if not found. */ @Nullable public JMethod getConstructor (@Nonnull final AbstractJType [] aArgTypes) { for (final JMethod m : m_aConstructors) if (m.hasSignature (aArgTypes)) return m; return null; } /** * Add a method to the list of method members of this JDefinedClass instance. * * @param mods * Modifiers for this method * @param type * Return type for this method * @param name * Name of the method * @return Newly generated {@link JMethod} */ @Nonnull public JMethod method (final int mods, @Nonnull final AbstractJType type, @Nonnull final String name) { // XXX problems caught in M constructor final JMethod m = new JMethod (this, mods, type, name); m_aMethods.add (m); return m; } @Nonnull public JMethod method (final int mods, final Class <?> type, final String name) { return method (mods, owner ()._ref (type), name); } /** * @return the set of methods defined in this class. */ @Nonnull public Collection <JMethod> methods () { return m_aMethods; } /** * Looks for a method that has the specified method signature and return it. * * @param sName * Method name to search * @param aArgTypes * Signature to search * @return <code>null</code> if not found. */ @Nullable public JMethod getMethod (final String sName, final AbstractJType [] aArgTypes) { for (final JMethod m : m_aMethods) if (m.name ().equals (sName)) if (m.hasSignature (aArgTypes)) return m; return null; } /** * @return <code>true</code> if a header comment (before the package) is * present, <code>false</code> if not. */ public boolean hasHeaderComment () { return m_aHeaderComment != null; } /** * @return The optional header comment that is emitted BEFORE the package (so * e.g. for license headers). Never <code>null</code>. */ @Nonnull public JDocComment headerComment () { if (m_aHeaderComment == null) m_aHeaderComment = new JDocComment (owner ()); return m_aHeaderComment; } @Nonnull public JDocComment javadoc () { if (m_aJDoc == null) m_aJDoc = new JDocComment (owner ()); return m_aJDoc; } /** * Mark this file as hidden, so that this file won't be generated. <br> * This feature could be used to generate code that refers to class X, without * actually generating X.java. */ public void hide () { m_bHideFile = true; } public boolean isHidden () { return m_bHideFile; } public void declare (@Nonnull final JFormatter f) { // Java docs if (m_aJDoc != null) f.newline ().generable (m_aJDoc); // Class annotations if (m_aAnnotations != null) for (final JAnnotationUse aAnnotation : m_aAnnotations) f.generable (aAnnotation).newline (); // Modifiers (private, protected, public) // Type of class (class, interface, enum, @interface) // Name of the class // Class wildcards f.generable (m_aMods).print (getClassType ().declarationToken ()).id (name ()).declaration (m_aGenerifiable); // If a super class is defined and is not "Object" boolean bHasSuperClass = false; if (m_aSuperClass != null && m_aSuperClass != owner ().ref (Object.class)) { bHasSuperClass = true; f.newline ().indent ().print ("extends").generable (m_aSuperClass).newline ().outdent (); } // Add all interfaces if (!m_aInterfaces.isEmpty ()) { if (!bHasSuperClass) f.newline (); f.indent ().print (isInterface () ? "extends" : "implements"); f.generable (m_aInterfaces); f.newline ().outdent (); } declareBody (f); } /** * prints the body of a class. * * @param f * Formatter to use */ protected void declareBody (@Nonnull final JFormatter f) { f.print ('{').newline ().indent (); boolean bFirst = true; if (!m_aEnumConstantsByName.isEmpty ()) { for (final JEnumConstant c : m_aEnumConstantsByName.values ()) { if (bFirst) bFirst = false; else f.print (',').newline (); f.declaration (c); } f.print (';').newline (); } // All fields for (final JFieldVar field : m_aFields.values ()) f.declaration (field); // Static init if (m_aStaticInit != null) f.newline ().print ("static").statement (m_aStaticInit); // Instance init if (m_aInstanceInit != null) f.newline ().statement (m_aInstanceInit); // All constructors for (final JMethod m : m_aConstructors) f.newline ().declaration (m); // All regular methods for (final JMethod m : m_aMethods) f.newline ().declaration (m); // All inner classes if (m_aClasses != null) for (final JDefinedClass dc : m_aClasses.values ()) f.newline ().declaration (dc); // Hacks... if (m_sDirectBlock != null) f.print (m_sDirectBlock); f.outdent ().print ('}').newline (); } /** * Places the given string directly inside the generated class. This method * can be used to add methods/fields that are not generated by CodeModel. This * method should be used only as the last resort. * * @param string * Direct code block */ public void direct (@Nullable final String string) { if (m_sDirectBlock == null) m_sDirectBlock = string; else if (string != null) m_sDirectBlock += string; } @Override @Nonnull public final JPackage _package () { IJClassContainer <?> p = getOuter (); while (!(p instanceof JPackage)) p = p.parentContainer (); return (JPackage) p; } @Nonnull public JTypeVar generify (@Nonnull final String name) { return m_aGenerifiable.generify (name); } @Nonnull public JTypeVar generify (@Nonnull final String name, @Nonnull final Class <?> bound) { return m_aGenerifiable.generify (name, bound); } @Nonnull public JTypeVar generify (@Nonnull final String name, @Nonnull final AbstractJClass bound) { return m_aGenerifiable.generify (name, bound); } @Override @Nonnull public JTypeVar [] typeParams () { return m_aGenerifiable.typeParams (); } @Nonnull public List <JTypeVar> typeParamList () { return m_aGenerifiable.typeParamList (); } @Override protected AbstractJClass substituteParams (final JTypeVar [] variables, final List <? extends AbstractJClass> bindings) { return this; } @Nonnull public JAnnotationUse annotate (@Nonnull final Class <? extends Annotation> clazz) { return annotate (owner ().ref (clazz)); } @Nonnull public JAnnotationUse annotate (@Nonnull final AbstractJClass clazz) { if (m_aAnnotations == null) m_aAnnotations = new ArrayList <> (); final JAnnotationUse a = new JAnnotationUse (clazz); m_aAnnotations.add (a); return a; } @Nonnull public <W extends IJAnnotationWriter <?>> W annotate2 (@Nonnull final Class <W> clazz) { return TypedAnnotationWriter.create (clazz, this); } @Nonnull public Collection <JAnnotationUse> annotations () { if (m_aAnnotations == null) m_aAnnotations = new ArrayList <> (); return Collections.unmodifiableCollection (m_aAnnotations); } @Nullable public JAnnotationUse getAnnotation (final Class <?> annotationClass) { for (final JAnnotationUse jannotation : m_aAnnotations) { final AbstractJClass jannotationClass = jannotation.getAnnotationClass (); if (!jannotationClass.isError ()) { final String qualifiedName = jannotationClass.fullName (); if (qualifiedName != null && qualifiedName.equals (annotationClass.getName ())) { return jannotation; } } } return null; } @Override @Nonnull protected JDefinedClass createInnerClass (final int nMods, final EClassType eClassType, final String sName) { return new JDefinedClass (this, nMods, sName, eClassType); } /** * Returns true if this class or it's inner classes contains references to * error-types. * * @return <code>true</code> if an error type is contained, <code>false</code> * otherwise * @see JErrorClass */ public boolean containsErrorTypes () { return JFormatter.containsErrorTypes (this); } }