1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.codemodel.internal;
27
28import java.io.File;
29import java.io.IOException;
30import java.io.PrintStream;
31import java.lang.reflect.Modifier;
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.Iterator;
36import java.util.List;
37import java.util.Map;
38
39import com.sun.codemodel.internal.writer.FileCodeWriter;
40import com.sun.codemodel.internal.writer.ProgressCodeWriter;
41
42/**
43 * Root of the code DOM.
44 *
45 * <p>
46 * Here's your typical CodeModel application.
47 *
48 * <pre>
49 * JCodeModel cm = new JCodeModel();
50 *
51 * // generate source code by populating the 'cm' tree.
52 * cm._class(...);
53 * ...
54 *
55 * // write them out
56 * cm.build(new File("."));
57 * </pre>
58 *
59 * <p>
60 * Every CodeModel node is always owned by one {@link JCodeModel} object
61 * at any given time (which can be often accesesd by the {@code owner()} method.)
62 *
63 * As such, when you generate Java code, most of the operation works
64 * in a top-down fashion. For example, you create a class from {@link JCodeModel},
65 * which gives you a {@link JDefinedClass}. Then you invoke a method on it
66 * to generate a new method, which gives you {@link JMethod}, and so on.
67 *
68 * There are a few exceptions to this, most notably building {@link JExpression}s,
69 * but generally you work with CodeModel in a top-down fashion.
70 *
71 * Because of this design, most of the CodeModel classes aren't directly instanciable.
72 *
73 *
74 * <h2>Where to go from here?</h2>
75 * <p>
76 * Most of the time you'd want to populate new type definitions in a {@link JCodeModel}.
77 * See {@link #_class(String, ClassType)}.
78 */
79public final class JCodeModel {
80
81    /** The packages that this JCodeWriter contains. */
82    private final HashMap<String,JPackage> packages = new HashMap<>();
83
84    /** Java module in {@code module-info.java} file. */
85    private JModule module;
86
87    /** All JReferencedClasses are pooled here. */
88    private final HashMap<Class<?>,JReferencedClass> refClasses = new HashMap<>();
89
90
91    /** Obtains a reference to the special "null" type. */
92    public final JNullType NULL = new JNullType(this);
93    // primitive types
94    public final JPrimitiveType VOID    = new JPrimitiveType(this,"void",   Void.class);
95    public final JPrimitiveType BOOLEAN = new JPrimitiveType(this,"boolean",Boolean.class);
96    public final JPrimitiveType BYTE    = new JPrimitiveType(this,"byte",   Byte.class);
97    public final JPrimitiveType SHORT   = new JPrimitiveType(this,"short",  Short.class);
98    public final JPrimitiveType CHAR    = new JPrimitiveType(this,"char",   Character.class);
99    public final JPrimitiveType INT     = new JPrimitiveType(this,"int",    Integer.class);
100    public final JPrimitiveType FLOAT   = new JPrimitiveType(this,"float",  Float.class);
101    public final JPrimitiveType LONG    = new JPrimitiveType(this,"long",   Long.class);
102    public final JPrimitiveType DOUBLE  = new JPrimitiveType(this,"double", Double.class);
103
104    /**
105     * If the flag is true, we will consider two classes "Foo" and "foo"
106     * as a collision.
107     */
108    protected static final boolean isCaseSensitiveFileSystem = getFileSystemCaseSensitivity();
109
110    private static boolean getFileSystemCaseSensitivity() {
111        try {
112            // let the system property override, in case the user really
113            // wants to override.
114            if( System.getProperty("com.sun.codemodel.internal.FileSystemCaseSensitive")!=null )
115                return true;
116        } catch( Exception e ) {}
117
118        // on Unix, it's case sensitive.
119        return (File.separatorChar == '/');
120    }
121
122
123    public JCodeModel() {}
124
125    /**
126     * Add a package to the list of packages to be generated.
127     *
128     * @param name
129     *        Name of the package. Use "" to indicate the root package.
130     *
131     * @return Newly generated package
132     */
133    public JPackage _package(String name) {
134        JPackage p = packages.get(name);
135        if (p == null) {
136            p = new JPackage(name, this);
137            packages.put(name, p);
138        }
139        return p;
140    }
141
142    /**
143     * Creates and returns Java module to be generated.
144     * @param name The Name of Java module.
145     * @return New Java module.
146     */
147    public JModule _moduleInfo(final String name) {
148        return module = new JModule(name);
149    }
150
151    /**
152     * Returns existing Java module to be generated.
153     * @return Java module or {@code null} if Java module was not created yet.
154     */
155    public JModule _getModuleInfo() {
156        return module;
157    }
158
159    /**
160     * Creates Java module instance and adds existing packages with classes to the Java module info.
161     * Used to initialize and build Java module instance with existing packages content.
162     * @param name The Name of Java module.
163     * @param requires Requires directives to add.
164     * @throws IllegalStateException when Java module instance was not initialized.
165     */
166    public void _prepareModuleInfo(final String name, final String ...requires) {
167        _moduleInfo(name);
168        _updateModuleInfo(requires);
169    }
170
171    /**
172     * Adds existing packages with classes to the Java module info.
173     * Java module instance must exist before calling this method.
174     * Used to update Java module instance with existing packages content after it was prepared on client side.
175     * @param requires Requires directives to add.
176     * @throws IllegalStateException when Java module instance was not initialized.
177     */
178    public void _updateModuleInfo(final String ...requires) {
179        if (module == null) {
180            throw new IllegalStateException("Java module instance was not initialized yet.");
181        }
182        module._exports(packages.values(), false);
183        module._requires(requires);
184    }
185
186    public final JPackage rootPackage() {
187        return _package("");
188    }
189
190    /**
191     * Returns an iterator that walks the packages defined using this code
192     * writer.
193     */
194    public Iterator<JPackage> packages() {
195        return packages.values().iterator();
196    }
197
198    /**
199     * Creates a new generated class.
200     *
201     * @exception JClassAlreadyExistsException
202     *      When the specified class/interface was already created.
203     */
204    public JDefinedClass _class(String fullyqualifiedName) throws JClassAlreadyExistsException {
205        return _class(fullyqualifiedName,ClassType.CLASS);
206    }
207
208    /**
209     * Creates a dummy, unknown {@link JClass} that represents a given name.
210     *
211     * <p>
212     * This method is useful when the code generation needs to include the user-specified
213     * class that may or may not exist, and only thing known about it is a class name.
214     */
215    public JClass directClass(String name) {
216        return new JDirectClass(this,name);
217    }
218
219    /**
220     * Creates a new generated class.
221     *
222     * @exception JClassAlreadyExistsException
223     *      When the specified class/interface was already created.
224     */
225    public JDefinedClass _class(int mods, String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
226        int idx = fullyqualifiedName.lastIndexOf('.');
227        if( idx<0 )     return rootPackage()._class(fullyqualifiedName);
228        else
229            return _package(fullyqualifiedName.substring(0,idx))
230                ._class(mods, fullyqualifiedName.substring(idx+1), t );
231    }
232
233    /**
234     * Creates a new generated class.
235     *
236     * @exception JClassAlreadyExistsException
237     *      When the specified class/interface was already created.
238     */
239    public JDefinedClass _class(String fullyqualifiedName,ClassType t) throws JClassAlreadyExistsException {
240        return _class( JMod.PUBLIC, fullyqualifiedName, t );
241    }
242
243    /**
244     * Gets a reference to the already created generated class.
245     *
246     * @return null
247     *      If the class is not yet created.
248     * @see JPackage#_getClass(String)
249     */
250    public JDefinedClass _getClass(String fullyQualifiedName) {
251        int idx = fullyQualifiedName.lastIndexOf('.');
252        if( idx<0 )     return rootPackage()._getClass(fullyQualifiedName);
253        else
254            return _package(fullyQualifiedName.substring(0,idx))
255                ._getClass( fullyQualifiedName.substring(idx+1) );
256    }
257
258    /**
259     * Creates a new anonymous class.
260     *
261     * @deprecated
262     *      The naming convention doesn't match the rest of the CodeModel.
263     *      Use {@link #anonymousClass(JClass)} instead.
264     */
265    public JDefinedClass newAnonymousClass(JClass baseType) {
266        return new JAnonymousClass(baseType);
267    }
268
269    /**
270     * Creates a new anonymous class.
271     */
272    public JDefinedClass anonymousClass(JClass baseType) {
273        return new JAnonymousClass(baseType);
274    }
275
276    public JDefinedClass anonymousClass(Class<?> baseType) {
277        return anonymousClass(ref(baseType));
278    }
279
280    /**
281     * Generates Java source code.
282     * A convenience method for <code>build(destDir,destDir,System.out)</code>.
283     *
284     * @param   destDir
285     *          source files are generated into this directory.
286     * @param   status
287     *      if non-null, progress indication will be sent to this stream.
288     */
289    public void build( File destDir, PrintStream status ) throws IOException {
290        build(destDir,destDir,status);
291    }
292
293    /**
294     * Generates Java source code.
295     * A convenience method that calls {@link #build(CodeWriter,CodeWriter)}.
296     *
297     * @param   srcDir
298     *          Java source files are generated into this directory.
299     * @param   resourceDir
300     *          Other resource files are generated into this directory.
301     * @param   status
302     *      if non-null, progress indication will be sent to this stream.
303     */
304    public void build( File srcDir, File resourceDir, PrintStream status ) throws IOException {
305        CodeWriter src = new FileCodeWriter(srcDir);
306        CodeWriter res = new FileCodeWriter(resourceDir);
307        if(status!=null) {
308            src = new ProgressCodeWriter(src, status );
309            res = new ProgressCodeWriter(res, status );
310        }
311        build(src,res);
312    }
313
314    /**
315     * A convenience method for <code>build(destDir,System.out)</code>.
316     */
317    public void build( File destDir ) throws IOException {
318        build(destDir,System.out);
319    }
320
321    /**
322     * A convenience method for <code>build(srcDir,resourceDir,System.out)</code>.
323     */
324    public void build( File srcDir, File resourceDir ) throws IOException {
325        build(srcDir,resourceDir,System.out);
326    }
327
328    /**
329     * A convenience method for <code>build(out,out)</code>.
330     */
331    public void build( CodeWriter out ) throws IOException {
332        build(out,out);
333    }
334
335    /**
336     * Generates Java source code.
337     */
338    public void build( CodeWriter source, CodeWriter resource ) throws IOException {
339        JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
340        // avoid concurrent modification exception
341        for( JPackage pkg : pkgs ) {
342            pkg.build(source,resource);
343        }
344        if (module != null) {
345            module.build(source);
346        }
347        source.close();
348        resource.close();
349    }
350
351    /**
352     * Returns the number of files to be generated if
353     * {@link #build} is invoked now.
354     */
355    public int countArtifacts() {
356        int r = 0;
357        JPackage[] pkgs = packages.values().toArray(new JPackage[packages.size()]);
358        // avoid concurrent modification exception
359        for( JPackage pkg : pkgs )
360            r += pkg.countArtifacts();
361        return r;
362    }
363
364
365    /**
366     * Obtains a reference to an existing class from its Class object.
367     *
368     * <p>
369     * The parameter may not be primitive.
370     *
371     * @see #_ref(Class) for the version that handles more cases.
372     */
373    public JClass ref(Class<?> clazz) {
374        JReferencedClass jrc = (JReferencedClass)refClasses.get(clazz);
375        if (jrc == null) {
376            if (clazz.isPrimitive())
377                throw new IllegalArgumentException(clazz+" is a primitive");
378            if (clazz.isArray()) {
379                return new JArrayClass(this, _ref(clazz.getComponentType()));
380            } else {
381                jrc = new JReferencedClass(clazz);
382                refClasses.put(clazz, jrc);
383            }
384        }
385        return jrc;
386    }
387
388    public JType _ref(Class<?> c) {
389        if(c.isPrimitive())
390            return JType.parse(this,c.getName());
391        else
392            return ref(c);
393    }
394
395    /**
396     * Obtains a reference to an existing class from its fully-qualified
397     * class name.
398     *
399     * <p>
400     * First, this method attempts to load the class of the given name.
401     * If that fails, we assume that the class is derived straight from
402     * {@link Object}, and return a {@link JClass}.
403     */
404    public JClass ref(String fullyQualifiedClassName) {
405        try {
406            // try the context class loader first
407            return ref(SecureLoader.getContextClassLoader().loadClass(fullyQualifiedClassName));
408        } catch (ClassNotFoundException e) {
409            // fall through
410        }
411        // then the default mechanism.
412        try {
413            return ref(Class.forName(fullyQualifiedClassName));
414        } catch (ClassNotFoundException e1) {
415            // fall through
416        }
417
418        // assume it's not visible to us.
419        return new JDirectClass(this,fullyQualifiedClassName);
420    }
421
422    /**
423     * Cached for {@link #wildcard()}.
424     */
425    private JClass wildcard;
426
427    /**
428     * Gets a {@link JClass} representation for "?",
429     * which is equivalent to "? extends Object".
430     */
431    public JClass wildcard() {
432        if(wildcard==null)
433            wildcard = ref(Object.class).wildcard();
434        return wildcard;
435    }
436
437    /**
438     * Obtains a type object from a type name.
439     *
440     * <p>
441     * This method handles primitive types, arrays, and existing {@link Class}es.
442     *
443     * @exception ClassNotFoundException
444     *      If the specified type is not found.
445     */
446    public JType parseType(String name) throws ClassNotFoundException {
447        // array
448        if(name.endsWith("[]"))
449            return parseType(name.substring(0,name.length()-2)).array();
450
451        // try primitive type
452        try {
453            return JType.parse(this,name);
454        } catch (IllegalArgumentException e) {
455            ;
456        }
457
458        // existing class
459        return new TypeNameParser(name).parseTypeName();
460    }
461
462    private final class TypeNameParser {
463        private final String s;
464        private int idx;
465
466        public TypeNameParser(String s) {
467            this.s = s;
468        }
469
470        /**
471         * Parses a type name token T (which can be potentially of the form Tr&ly;T1,T2,...>,
472         * or "? extends/super T".)
473         *
474         * @return the index of the character next to T.
475         */
476        JClass parseTypeName() throws ClassNotFoundException {
477            int start = idx;
478
479            if(s.charAt(idx)=='?') {
480                // wildcard
481                idx++;
482                ws();
483                String head = s.substring(idx);
484                if(head.startsWith("extends")) {
485                    idx+=7;
486                    ws();
487                    return parseTypeName().wildcard();
488                } else
489                if(head.startsWith("super")) {
490                    throw new UnsupportedOperationException("? super T not implemented");
491                } else {
492                    // not supported
493                    throw new IllegalArgumentException("only extends/super can follow ?, but found "+s.substring(idx));
494                }
495            }
496
497            while(idx<s.length()) {
498                char ch = s.charAt(idx);
499                if(Character.isJavaIdentifierStart(ch)
500                || Character.isJavaIdentifierPart(ch)
501                || ch=='.')
502                    idx++;
503                else
504                    break;
505            }
506
507            JClass clazz = ref(s.substring(start,idx));
508
509            return parseSuffix(clazz);
510        }
511
512        /**
513         * Parses additional left-associative suffixes, like type arguments
514         * and array specifiers.
515         */
516        private JClass parseSuffix(JClass clazz) throws ClassNotFoundException {
517            if(idx==s.length())
518                return clazz; // hit EOL
519
520            char ch = s.charAt(idx);
521
522            if(ch=='<')
523                return parseSuffix(parseArguments(clazz));
524
525            if(ch=='[') {
526                if(s.charAt(idx+1)==']') {
527                    idx+=2;
528                    return parseSuffix(clazz.array());
529                }
530                throw new IllegalArgumentException("Expected ']' but found "+s.substring(idx+1));
531            }
532
533            return clazz;
534        }
535
536        /**
537         * Skips whitespaces
538         */
539        private void ws() {
540            while(Character.isWhitespace(s.charAt(idx)) && idx<s.length())
541                idx++;
542        }
543
544        /**
545         * Parses '&lt;T1,T2,...,Tn>'
546         *
547         * @return the index of the character next to '>'
548         */
549        private JClass parseArguments(JClass rawType) throws ClassNotFoundException {
550            if(s.charAt(idx)!='<')
551                throw new IllegalArgumentException();
552            idx++;
553
554            List<JClass> args = new ArrayList<JClass>();
555
556            while(true) {
557                args.add(parseTypeName());
558                if(idx==s.length())
559                    throw new IllegalArgumentException("Missing '>' in "+s);
560                char ch = s.charAt(idx);
561                if(ch=='>')
562                    return rawType.narrow(args.toArray(new JClass[args.size()]));
563
564                if(ch!=',')
565                    throw new IllegalArgumentException(s);
566                idx++;
567            }
568
569        }
570    }
571
572    /**
573     * References to existing classes.
574     *
575     * <p>
576     * JReferencedClass is kept in a pool so that they are shared.
577     * There is one pool for each JCodeModel object.
578     *
579     * <p>
580     * It is impossible to cache JReferencedClass globally only because
581     * there is the _package() method, which obtains the owner JPackage
582     * object, which is scoped to JCodeModel.
583     */
584    private class JReferencedClass extends JClass implements JDeclaration {
585        private final Class<?> _class;
586
587        JReferencedClass(Class<?> _clazz) {
588            super(JCodeModel.this);
589            this._class = _clazz;
590            assert !_class.isArray();
591        }
592
593        public String name() {
594            return _class.getSimpleName().replace('$','.');
595        }
596
597        public String fullName() {
598            return _class.getName().replace('$','.');
599        }
600
601        public String binaryName() {
602            return _class.getName();
603        }
604
605        public JClass outer() {
606            Class<?> p = _class.getDeclaringClass();
607            if(p==null)     return null;
608            return ref(p);
609        }
610
611        public JPackage _package() {
612            String name = fullName();
613
614            // this type is array
615            if (name.indexOf('[') != -1)
616                return JCodeModel.this._package("");
617
618            // other normal case
619            int idx = name.lastIndexOf('.');
620            if (idx < 0)
621                return JCodeModel.this._package("");
622            else
623                return JCodeModel.this._package(name.substring(0, idx));
624        }
625
626        public JClass _extends() {
627            Class<?> sp = _class.getSuperclass();
628            if (sp == null) {
629                if(isInterface())
630                    return owner().ref(Object.class);
631                return null;
632            } else
633                return ref(sp);
634        }
635
636        public Iterator<JClass> _implements() {
637            final Class<?>[] interfaces = _class.getInterfaces();
638            return new Iterator<JClass>() {
639                private int idx = 0;
640                public boolean hasNext() {
641                    return idx < interfaces.length;
642                }
643                public JClass next() {
644                    return JCodeModel.this.ref(interfaces[idx++]);
645                }
646                public void remove() {
647                    throw new UnsupportedOperationException();
648                }
649            };
650        }
651
652        public boolean isInterface() {
653            return _class.isInterface();
654        }
655
656        public boolean isAbstract() {
657            return Modifier.isAbstract(_class.getModifiers());
658        }
659
660        public JPrimitiveType getPrimitiveType() {
661            Class<?> v = boxToPrimitive.get(_class);
662            if(v!=null)
663                return JType.parse(JCodeModel.this,v.getName());
664            else
665                return null;
666        }
667
668        public boolean isArray() {
669            return false;
670        }
671
672        public void declare(JFormatter f) {
673        }
674
675        public JTypeVar[] typeParams() {
676            // TODO: does JDK 1.5 reflection provides these information?
677            return super.typeParams();
678        }
679
680        protected JClass substituteParams(JTypeVar[] variables, List<JClass> bindings) {
681            // TODO: does JDK 1.5 reflection provides these information?
682            return this;
683        }
684    }
685
686    /**
687     * Conversion from primitive type {@link Class} (such as {@link Integer#TYPE}
688     * to its boxed type (such as {@code Integer.class})
689     */
690    public static final Map<Class<?>,Class<?>> primitiveToBox;
691    /**
692     * The reverse look up for {@link #primitiveToBox}
693     */
694    public static final Map<Class<?>,Class<?>> boxToPrimitive;
695
696    static {
697        Map<Class<?>,Class<?>> m1 = new HashMap<Class<?>,Class<?>>();
698        Map<Class<?>,Class<?>> m2 = new HashMap<Class<?>,Class<?>>();
699
700        m1.put(Boolean.class,Boolean.TYPE);
701        m1.put(Byte.class,Byte.TYPE);
702        m1.put(Character.class,Character.TYPE);
703        m1.put(Double.class,Double.TYPE);
704        m1.put(Float.class,Float.TYPE);
705        m1.put(Integer.class,Integer.TYPE);
706        m1.put(Long.class,Long.TYPE);
707        m1.put(Short.class,Short.TYPE);
708        m1.put(Void.class,Void.TYPE);
709
710        for (Map.Entry<Class<?>, Class<?>> e : m1.entrySet())
711            m2.put(e.getValue(),e.getKey());
712
713        boxToPrimitive = Collections.unmodifiableMap(m1);
714        primitiveToBox = Collections.unmodifiableMap(m2);
715
716    }
717}
718