1/*
2 * Copyright 2016 Google Inc. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.turbine.binder;
18
19import com.google.common.base.Joiner;
20import com.google.common.base.Optional;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.ImmutableMap;
23import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Iterables;
25import com.google.turbine.binder.bound.SourceBoundClass;
26import com.google.turbine.binder.sym.ClassSymbol;
27import com.google.turbine.diag.SourceFile;
28import com.google.turbine.model.TurbineFlag;
29import com.google.turbine.model.TurbineTyKind;
30import com.google.turbine.tree.Tree;
31import com.google.turbine.tree.Tree.CompUnit;
32import com.google.turbine.tree.Tree.ImportDecl;
33import com.google.turbine.tree.Tree.PkgDecl;
34import com.google.turbine.tree.Tree.TyDecl;
35import com.google.turbine.tree.TurbineModifier;
36import java.util.List;
37
38/**
39 * Processes compilation units before binding, creating symbols for type declarations and desugaring
40 * access modifiers.
41 */
42public class CompUnitPreprocessor {
43
44  /** A pre-processed compilation unit. */
45  public static class PreprocessedCompUnit {
46    private final ImmutableList<Tree.ImportDecl> imports;
47    private final ImmutableList<SourceBoundClass> types;
48    private final SourceFile source;
49    private final String packageName;
50
51    public PreprocessedCompUnit(
52        ImmutableList<ImportDecl> imports,
53        ImmutableList<SourceBoundClass> types,
54        SourceFile source,
55        String packageName) {
56      this.imports = imports;
57      this.types = types;
58      this.source = source;
59      this.packageName = packageName;
60    }
61
62    public ImmutableList<ImportDecl> imports() {
63      return imports;
64    }
65
66    public ImmutableList<SourceBoundClass> types() {
67      return types;
68    }
69
70    public SourceFile source() {
71      return source;
72    }
73
74    public String packageName() {
75      return packageName;
76    }
77  }
78
79  public static ImmutableList<PreprocessedCompUnit> preprocess(List<CompUnit> units) {
80    ImmutableList.Builder<PreprocessedCompUnit> result = ImmutableList.builder();
81    for (CompUnit unit : units) {
82      result.add(preprocess(unit));
83    }
84    return result.build();
85  }
86
87  public static PreprocessedCompUnit preprocess(CompUnit unit) {
88    String packageName;
89    Iterable<TyDecl> decls = unit.decls();
90    if (unit.pkg().isPresent()) {
91      packageName = Joiner.on('/').join(unit.pkg().get().name());
92      // "While the file could technically contain the source code
93      // for one or more package-private (default-access) classes,
94      // it would be very bad form." -- JLS 7.4.1
95      if (!unit.pkg().get().annos().isEmpty()) {
96        decls = Iterables.concat(decls, ImmutableList.of(packageInfoTree(unit.pkg().get())));
97      }
98    } else {
99      packageName = "";
100    }
101    ImmutableList.Builder<SourceBoundClass> types = ImmutableList.builder();
102    for (TyDecl decl : decls) {
103      ClassSymbol sym =
104          new ClassSymbol((!packageName.isEmpty() ? packageName + "/" : "") + decl.name());
105      int access = access(decl.mods(), decl.tykind());
106      ImmutableMap<String, ClassSymbol> children =
107          preprocessChildren(types, sym, decl.members(), access);
108      types.add(new SourceBoundClass(sym, null, children, access, decl));
109    }
110    return new PreprocessedCompUnit(unit.imports(), types.build(), unit.source(), packageName);
111  }
112
113  private static ImmutableMap<String, ClassSymbol> preprocessChildren(
114      ImmutableList.Builder<SourceBoundClass> types,
115      ClassSymbol owner,
116      ImmutableList<Tree> members,
117      int enclosing) {
118    ImmutableMap.Builder<String, ClassSymbol> result = ImmutableMap.builder();
119    for (Tree member : members) {
120      if (member.kind() == Tree.Kind.TY_DECL) {
121        Tree.TyDecl decl = (Tree.TyDecl) member;
122        ClassSymbol sym = new ClassSymbol(owner.binaryName() + '$' + decl.name());
123        result.put(decl.name(), sym);
124
125        int access = innerClassAccess(enclosing, decl);
126
127        ImmutableMap<String, ClassSymbol> children =
128            preprocessChildren(types, sym, decl.members(), access);
129        types.add(new SourceBoundClass(sym, owner, children, access, decl));
130      }
131    }
132    return result.build();
133  }
134
135  /** Desugars access flags for a class. */
136  public static int access(ImmutableSet<TurbineModifier> mods, TurbineTyKind tykind) {
137    int access = 0;
138    for (TurbineModifier m : mods) {
139      access |= m.flag();
140    }
141    switch (tykind) {
142      case CLASS:
143        access |= TurbineFlag.ACC_SUPER;
144        break;
145      case INTERFACE:
146        access |= TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_INTERFACE;
147        break;
148      case ENUM:
149        // Assuming all enums are final is safe, because nothing outside
150        // the compilation unit can extend abstract enums anyways, and
151        // refactoring an existing enum to implement methods in the container
152        // class instead of the constants is not a breaking change.
153        access |= TurbineFlag.ACC_SUPER | TurbineFlag.ACC_ENUM | TurbineFlag.ACC_FINAL;
154        break;
155      case ANNOTATION:
156        access |= TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION;
157        break;
158    }
159    return access;
160  }
161
162  /** Desugars access flags for an inner class. */
163  private static int innerClassAccess(int enclosing, TyDecl decl) {
164    int access = access(decl.mods(), decl.tykind());
165
166    // types declared in interfaces and annotations are implicitly public (JLS 9.5)
167    if ((enclosing & (TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION)) != 0) {
168      access &= ~(TurbineFlag.ACC_PRIVATE | TurbineFlag.ACC_PROTECTED);
169      access |= TurbineFlag.ACC_PUBLIC;
170    }
171
172    // Nested enums, interfaces, and annotations, and any types nested within interfaces and
173    // annotations (JLS 9.5) are implicitly static.
174    switch (decl.tykind()) {
175      case INTERFACE:
176      case ENUM:
177      case ANNOTATION:
178        access |= TurbineFlag.ACC_STATIC;
179        break;
180      case CLASS:
181        if ((enclosing & (TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION)) != 0) {
182          access |= TurbineFlag.ACC_STATIC;
183        }
184    }
185
186    // propagate strictfp to nested types
187    access |= (enclosing & TurbineFlag.ACC_STRICT);
188    return access;
189  }
190
191  /** package-info.java's are desugared into synthetic class declarations. */
192  private static TyDecl packageInfoTree(PkgDecl pkgDecl) {
193    return new TyDecl(
194        pkgDecl.position(),
195        ImmutableSet.of(TurbineModifier.ACC_SYNTHETIC),
196        pkgDecl.annos(),
197        "package-info",
198        ImmutableList.of(),
199        Optional.absent(),
200        ImmutableList.of(),
201        ImmutableList.of(),
202        TurbineTyKind.INTERFACE);
203  }
204}
205