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

com.sun.codemodel.JCodeModel Maven / Gradle / Ivy

There is a newer version: 4.0.5
Show newest version
/*
 * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package com.sun.codemodel;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.sun.codemodel.writer.FileCodeWriter;
import com.sun.codemodel.writer.ProgressCodeWriter;

/**
 * Root of the code DOM.
 *
 * 

* Here's your typical CodeModel application. * *

{@code
 * JCodeModel cm = new JCodeModel();
 *
 * // generate source code by populating the 'cm' tree.
 * cm._class(...);
 * ...
 *
 * // write them out
 * cm.build(new File("."));
 * }
* *

* Every CodeModel node is always owned by one {@link JCodeModel} object * at any given time (which can be often accesesd by the {@code owner()} method.) * * As such, when you generate Java code, most of the operation works * in a top-down fashion. For example, you create a class from {@link JCodeModel}, * which gives you a {@link JDefinedClass}. Then you invoke a method on it * to generate a new method, which gives you {@link JMethod}, and so on. * * There are a few exceptions to this, most notably building {@link JExpression}s, * but generally you work with CodeModel in a top-down fashion. * * Because of this design, most of the CodeModel classes aren't directly instanciable. * * *

Where to go from here?

*

* Most of the time you'd want to populate new type definitions in a {@link JCodeModel}. * See {@link #_class(String, ClassType)}. */ public final class JCodeModel { /** The packages that this JCodeWriter contains. */ private final HashMap packages = new HashMap<>(); /** Java module in {@code module-info.java} file. */ private JModule module; /** All JReferencedClasses are pooled here. */ private final HashMap,JReferencedClass> refClasses = new HashMap<>(); private final Map classNameReplacer = new HashMap<>(); /** Obtains a reference to the special "null" type. */ public final JNullType NULL = new JNullType(this); // primitive types public final JPrimitiveType VOID = new JPrimitiveType(this,"void", Void.class); public final JPrimitiveType BOOLEAN = new JPrimitiveType(this,"boolean",Boolean.class); public final JPrimitiveType BYTE = new JPrimitiveType(this,"byte", Byte.class); public final JPrimitiveType SHORT = new JPrimitiveType(this,"short", Short.class); public final JPrimitiveType CHAR = new JPrimitiveType(this,"char", Character.class); public final JPrimitiveType INT = new JPrimitiveType(this,"int", Integer.class); public final JPrimitiveType FLOAT = new JPrimitiveType(this,"float", Float.class); public final JPrimitiveType LONG = new JPrimitiveType(this,"long", Long.class); public final JPrimitiveType DOUBLE = new JPrimitiveType(this,"double", Double.class); /** * If the flag is true, we will consider two classes "Foo" and "foo" * as a collision. */ /* package */ static final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity(); private static boolean getFileSystemCaseSensitivity() { try { // let the system property override, in case the user really // wants to override. if( System.getProperty("com.sun.codemodel.FileSystemCaseSensitive")!=null ) return true; } catch( Exception e ) {} // on Unix, it's case sensitive. return (File.separatorChar == '/'); } public JCodeModel() {} /** * Add a package to the list of packages to be generated. * * @param name * Name of the package. Use "" to indicate the root package. * * @return Newly generated package */ public JPackage _package(String name) { JPackage p = packages.get(name); if (p == null) { p = new JPackage(name, this); packages.put(name, p); } return p; } /** * Creates and returns Java module to be generated. * @param name The Name of Java module. * @return New Java module. */ public JModule _moduleInfo(final String name) { return module = new JModule(name); } /** * Returns existing Java module to be generated. * @return Java module or {@code null} if Java module was not created yet. */ public JModule _getModuleInfo() { return module; } /** * Creates Java module instance and adds existing packages with classes to the Java module info. * Used to initialize and build Java module instance with existing packages content. * @param name The Name of Java module. * @param requires Requires directives to add. * @throws IllegalStateException when Java module instance was not initialized. */ public void _prepareModuleInfo(final String name, final String ...requires) { _moduleInfo(name); _updateModuleInfo(requires); } /** * Adds existing packages with classes to the Java module info. * Java module instance must exist before calling this method. * Used to update Java module instance with existing packages content after it was prepared on client side. * @param requires Requires directives to add. * @throws IllegalStateException when Java module instance was not initialized. */ public void _updateModuleInfo(final String ...requires) { if (module == null) { throw new IllegalStateException("Java module instance was not initialized yet."); } module._exports(packages.values(), false); module._requires(requires); } public JPackage rootPackage() { return _package(""); } /** * Returns an iterator that walks the packages defined using this code * writer. */ public Iterator packages() { return packages.values().iterator(); } /** * Creates a new generated class. * * @exception JClassAlreadyExistsException * When the specified class/interface was already created. */ public JDefinedClass _class(String fullyqualifiedName) throws JClassAlreadyExistsException { return _class(fullyqualifiedName,ClassType.CLASS); } /** * Creates a dummy, unknown {@link JClass} that represents a given name. * *

* This method is useful when the code generation needs to include the user-specified * class that may or may not exist, and only thing known about it is a class name. */ public JClass directClass(String name) { return new JDirectClass(this,name); } /** * Creates a new generated class. * * @exception JClassAlreadyExistsException * When the specified class/interface was already created. */ public JDefinedClass _class(int mods, String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException { int idx = fullyqualifiedName.lastIndexOf('.'); if( idx<0 ) return rootPackage()._class(fullyqualifiedName); else return _package(fullyqualifiedName.substring(0,idx)) ._class(mods, fullyqualifiedName.substring(idx+1), t ); } /** * Creates a new generated class. * * @exception JClassAlreadyExistsException * When the specified class/interface was already created. */ public JDefinedClass _class(String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException { return _class( JMod.PUBLIC, fullyqualifiedName, t ); } /** * Gets a reference to the already created generated class. * * @return null * If the class is not yet created. * @see JPackage#_getClass(String) */ public JDefinedClass _getClass(String fullyQualifiedName) { int idx = fullyQualifiedName.lastIndexOf('.'); if( idx<0 ) return rootPackage()._getClass(fullyQualifiedName); else return _package(fullyQualifiedName.substring(0,idx)) ._getClass( fullyQualifiedName.substring(idx+1) ); } /** * Creates a new anonymous class. * * @deprecated * The naming convention doesn't match the rest of the CodeModel. * Use {@link #anonymousClass(JClass)} instead. */ @Deprecated public JDefinedClass newAnonymousClass(JClass baseType) { return new JAnonymousClass(baseType); } /** * Creates a new anonymous class. */ public JDefinedClass anonymousClass(JClass baseType) { return new JAnonymousClass(baseType); } public JDefinedClass anonymousClass(Class baseType) { return anonymousClass(ref(baseType)); } /** * Generates Java source code. * A convenience method for build(destDir,destDir,System.out). * * @param destDir * source files are generated into this directory. * @param status * if non-null, progress indication will be sent to this stream. */ public void build( File destDir, PrintStream status ) throws IOException { build(destDir,destDir,status); } /** * Generates Java source code. * A convenience method that calls {@link #build(CodeWriter,CodeWriter)}. * * @param srcDir * Java source files are generated into this directory. * @param resourceDir * Other resource files are generated into this directory. * @param status * if non-null, progress indication will be sent to this stream. */ public void build( File srcDir, File resourceDir, PrintStream status ) throws IOException { CodeWriter src = new FileCodeWriter(srcDir); CodeWriter res = new FileCodeWriter(resourceDir); if(status!=null) { src = new ProgressCodeWriter(src, status ); res = new ProgressCodeWriter(res, status ); } build(src,res); } /** * A convenience method for build(destDir,System.out). */ public void build( File destDir ) throws IOException { build(destDir,System.out); } /** * A convenience method for build(srcDir,resourceDir,System.out). */ public void build( File srcDir, File resourceDir ) throws IOException { build(srcDir,resourceDir,System.out); } /** * A convenience method for build(out,out). */ public void build( CodeWriter out ) throws IOException { build(out,out); } /** * Generates Java source code. */ public void build( CodeWriter source, CodeWriter resource ) throws IOException { JPackage[] pkgs = packages.values().toArray(new JPackage[0]); // avoid concurrent modification exception for( JPackage pkg : pkgs ) { pkg.build(source,resource); } if (module != null) { module.build(source); } source.close(); resource.close(); } /** * Returns the number of files to be generated if * {@link #build} is invoked now. */ public int countArtifacts() { int r = 0; JPackage[] pkgs = packages.values().toArray(new JPackage[0]); // avoid concurrent modification exception for( JPackage pkg : pkgs ) r += pkg.countArtifacts(); return r; } /** * Specify class names or packages to be replaced when the model is dumped into files. * @param c1 the regular expression to which class name or package will be replaced. * @param c2 the string to be substituted for the first match. */ public void addClassNameReplacer(String c1, String c2) { classNameReplacer.put(c1, c2); } /** * Gives an unmodifiable copy of classNameReplacer * @return classNameReplacer */ public Map classNameReplacer() { return Collections.unmodifiableMap(classNameReplacer); } /** * Obtains a reference to an existing class from its Class object. * *

* The parameter may not be primitive. * * @see #_ref(Class) for the version that handles more cases. */ public JClass ref(Class clazz) { JReferencedClass jrc = refClasses.get(clazz); if (jrc == null) { if (clazz.isPrimitive()) throw new IllegalArgumentException(clazz+" is a primitive"); if (clazz.isArray()) { return new JArrayClass(this, _ref(clazz.getComponentType())); } else { jrc = new JReferencedClass(clazz); refClasses.put(clazz, jrc); } } return jrc; } public JType _ref(Class c) { if(c.isPrimitive()) return JType.parse(this,c.getName()); else return ref(c); } /** * Obtains a reference to an existing class from its fully-qualified * class name. * *

* First, this method attempts to load the class of the given name. * If that fails, we assume that the class is derived straight from * {@link Object}, and return a {@link JClass}. */ public JClass ref(String fullyQualifiedClassName) { try { // try the context class loader first return ref(SecureLoader.getContextClassLoader().loadClass(fullyQualifiedClassName)); } catch (ClassNotFoundException e) { // fall through } // then the default mechanism. try { return ref(Class.forName(fullyQualifiedClassName)); } catch (ClassNotFoundException e1) { // fall through } // assume it's not visible to us. return new JDirectClass(this,fullyQualifiedClassName); } /** * Cached for {@link #wildcard()}. */ private JClass wildcard; /** * Gets a {@link JClass} representation for "?", * which is equivalent to "? extends Object". */ public JClass wildcard() { if(wildcard==null) wildcard = ref(Object.class).wildcard(); return wildcard; } /** * Obtains a type object from a type name. * *

* This method handles primitive types, arrays, and existing {@link Class}es. */ public JType parseType(String name) { // array if(name.endsWith("[]")) return parseType(name.substring(0,name.length()-2)).array(); // try primitive type try { return JType.parse(this,name); } catch (IllegalArgumentException e) { } // existing class // return new TypeNameParser(name).parseTypeName(); return new TreeParser().parseTypeName(name); } private class TreeParser { private Node buildTree(String str) { StringBuilder content = new StringBuilder(); Node root = new Node(null); root.value = str; Node current = root; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (c == '<') { Node child = new Node(current); current.value = content.toString(); current.childs.add(child); current = child; content = new StringBuilder(); } else if (c == '>') { if (current.value == null) { current.value = content.toString(); } current = current.parent; content = new StringBuilder(); } else if (c == ',') { if (current.value == null) { current.value = content.toString(); } Node brother = new Node(current.parent); brother.parent.childs.add(brother); current = brother; content = new StringBuilder(); } else { content.append(c); } } return root; } private void postOrderCreateJClass(Node node) { if (node != null) { for (Node child : node.childs) { postOrderCreateJClass(child); } node.jClass = new TypeNameParser(node.value).parseTypeName(); if (!node.childs.isEmpty()) { JClass[] argsA = node.childs.stream().map(n -> n.jClass).toArray(JClass[]::new); node.jClass = node.jClass.narrow(argsA); } } } private JClass parseTypeName(String str) { Node root = buildTree(str); postOrderCreateJClass(root); return root.jClass; } } private static class Node { private String value; private JClass jClass; private final Node parent; private final List childs = new LinkedList<>(); public Node(Node parent) { this.parent = parent; } @Override public String toString() { StringBuilder builder = new StringBuilder(value); boolean hasChilds = !childs.isEmpty(); if (hasChilds) { builder.append("<"); } for (Node child : childs) { builder.append(child.toString()).append(","); } if (hasChilds) { // Remove last comma builder.deleteCharAt(builder.length() - 1); builder.append(">"); } return builder.toString(); } } private final class TypeNameParser { private final String s; private int idx; public TypeNameParser(String s) { this.s = s; } /** * Parses a type name token T (which can be potentially of the form {@code T}, * or {@code ? extends/super T}.) * * @return the index of the character next to T. */ JClass parseTypeName() { int start = idx; if(s.charAt(idx)=='?') { // wildcard idx++; ws(); String head = s.substring(idx); if(head.startsWith("extends")) { idx+=7; ws(); return parseTypeName().wildcard(); } else if(head.startsWith("super")) { throw new UnsupportedOperationException("? super T not implemented"); } else { // not supported throw new IllegalArgumentException("only extends/super can follow ?, but found "+s.substring(idx)); } } while(idx'} * * @return the index of the character next to '{@literal >}' */ private JClass parseArguments(JClass rawType) { if(s.charAt(idx)!='<') throw new IllegalArgumentException(); idx++; List args = new ArrayList<>(); while(true) { args.add(parseTypeName()); if(idx==s.length()) throw new IllegalArgumentException("Missing '>' in "+s); char ch = s.charAt(idx); if(ch=='>') return rawType.narrow(args.toArray(new JClass[0])); if(ch!=',') throw new IllegalArgumentException(s); idx++; } } } /** * References to existing classes. * *

* JReferencedClass is kept in a pool so that they are shared. * There is one pool for each JCodeModel object. * *

* It is impossible to cache JReferencedClass globally only because * there is the _package() method, which obtains the owner JPackage * object, which is scoped to JCodeModel. */ private class JReferencedClass extends JClass implements JDeclaration { private final Class _class; JReferencedClass(Class _clazz) { super(JCodeModel.this); this._class = _clazz; assert !_class.isArray(); } @Override public String name() { return _class.getSimpleName().replace('$','.'); } @Override public String fullName() { return _class.getName().replace('$','.'); } @Override public String binaryName() { return _class.getName(); } @Override public JClass outer() { Class p = _class.getDeclaringClass(); if(p==null) return null; return ref(p); } @Override public JPackage _package() { String name = fullName(); // this type is array if (name.indexOf('[') != -1) return JCodeModel.this._package(""); // other normal case int idx = name.lastIndexOf('.'); if (idx < 0) return JCodeModel.this._package(""); else return JCodeModel.this._package(name.substring(0, idx)); } @Override public JClass _extends() { Class sp = _class.getSuperclass(); if (sp == null) { if(isInterface()) return owner().ref(Object.class); return null; } else return ref(sp); } @Override public Iterator _implements() { final Class[] interfaces = _class.getInterfaces(); return new Iterator<>() { private int idx = 0; @Override public boolean hasNext() { return idx < interfaces.length; } @Override public JClass next() { return JCodeModel.this.ref(interfaces[idx++]); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public boolean isInterface() { return _class.isInterface(); } @Override public boolean isAbstract() { return Modifier.isAbstract(_class.getModifiers()); } @Override public JPrimitiveType getPrimitiveType() { Class v = boxToPrimitive.get(_class); if(v!=null) return JType.parse(JCodeModel.this,v.getName()); else return null; } @Override public boolean isArray() { return false; } @Override public void declare(JFormatter f) { } @Override public JTypeVar[] typeParams() { // TODO: does JDK 1.5 reflection provides these information? return super.typeParams(); } @Override protected JClass substituteParams(JTypeVar[] variables, List bindings) { // TODO: does JDK 1.5 reflection provides these information? return this; } } /** * Conversion from primitive type {@link Class} (such as {@link Integer#TYPE} * to its boxed type (such as {@code Integer.class}) */ public static final Map,Class> primitiveToBox; /** * The reverse look up for {@link #primitiveToBox} */ public static final Map,Class> boxToPrimitive; static { Map,Class> m1 = new HashMap<>(); Map,Class> m2 = new HashMap<>(); m1.put(Boolean.class,Boolean.TYPE); m1.put(Byte.class,Byte.TYPE); m1.put(Character.class,Character.TYPE); m1.put(Double.class,Double.TYPE); m1.put(Float.class,Float.TYPE); m1.put(Integer.class,Integer.TYPE); m1.put(Long.class,Long.TYPE); m1.put(Short.class,Short.TYPE); m1.put(Void.class,Void.TYPE); for (Map.Entry, Class> e : m1.entrySet()) m2.put(e.getValue(),e.getKey()); boxToPrimitive = Collections.unmodifiableMap(m1); primitiveToBox = Collections.unmodifiableMap(m2); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy