View Javadoc
1   /*
2    * Copyright (C) 2016 Uwe Plonus
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package org.sw4j.tool.annotation.jpa.processor;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.IOException;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Properties;
26  import java.util.ServiceLoader;
27  import java.util.Set;
28  import javax.annotation.Nonnull;
29  import javax.annotation.Nullable;
30  import javax.annotation.processing.AbstractProcessor;
31  import javax.annotation.processing.ProcessingEnvironment;
32  import javax.annotation.processing.RoundEnvironment;
33  import javax.annotation.processing.SupportedAnnotationTypes;
34  import javax.annotation.processing.SupportedOptions;
35  import javax.annotation.processing.SupportedSourceVersion;
36  import javax.lang.model.SourceVersion;
37  import javax.lang.model.element.TypeElement;
38  import javax.persistence.Entity;
39  import javax.tools.Diagnostic;
40  import org.sw4j.tool.annotation.jpa.generator.GeneratorService;
41  import org.sw4j.tool.annotation.jpa.generator.model.Model;
42  
43  /**
44   * An annotation processor to handle JPA annotations.
45   *
46   * @author Uwe Plonus
47   */
48  @SupportedAnnotationTypes("javax.persistence.Entity")
49  @SupportedSourceVersion(SourceVersion.RELEASE_7)
50  @SupportedOptions(AnnotationProcessor.PROPERTIES_OPTION)
51  public class AnnotationProcessor extends AbstractProcessor {
52  
53      /** The option of the annotation processor to set output directory. */
54      public static final String PROPERTIES_OPTION = "tool.jpa.properties";
55  
56      /** The generated entity model. */
57      private final Model model;
58  
59      /** The processor to handle entities. */
60      private final EntityProcessor entityProcessor;
61  
62      private boolean firstRound = true;
63  
64      /**
65       * The default constructor.
66       */
67      public AnnotationProcessor() {
68          this.model = new Model();
69          this.entityProcessor = new EntityProcessor();
70      }
71  
72      /**
73       * Initializes the processor with the processing environment. An {@code IllegalStateException} will be thrown if
74       * this method is called more than once on the same object.
75       *
76       * @param processingEnv environment to access facilities the tool framework provides to the processor.
77       * @throws IllegalStateException if this method is called more than once.
78       */
79      @Override
80      public void init(@Nonnull final ProcessingEnvironment processingEnv) {
81          super.init(processingEnv);
82          this.entityProcessor.init(this.processingEnv);
83      }
84  
85      /**
86       * Processes the annotations given in the {@code annotations} variable.
87       *
88       * @param annotations the annotations that are handled.
89       * @param roundEnv the round environment to handle annotations.
90       * @return always {@code false} because this processor never claims an annotation.
91       */
92      @Override
93      public boolean process(@Nonnull final Set<? extends TypeElement> annotations,
94              @Nonnull final RoundEnvironment roundEnv) {
95          if (firstRound) {
96              this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Running JPA annotation processor");
97              firstRound = false;
98          }
99          String propertiesOption = this.processingEnv.getOptions().get(PROPERTIES_OPTION);
100         Map<String, Properties> properties = readProperties(propertiesOption);
101         this.entityProcessor.process(roundEnv.getElementsAnnotatedWith(Entity.class), model);
102 
103         if (roundEnv.processingOver()) {
104             ServiceLoader<GeneratorService> generatorServiceLoader = setupGenerators(properties);
105             Iterator<GeneratorService> generators = generatorServiceLoader.iterator();
106             while (generators.hasNext()) {
107                 GeneratorService generator = generators.next();
108                 if (generator.canProcess()) {
109                     try {
110                         generator.process(this.model, this.processingEnv);
111                     } catch (IOException ioex) {
112                         this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
113                                 ioex.toString());
114                     }
115                 }
116             }
117         }
118 
119         return false;
120     }
121 
122     /**
123      * Reads all properties from the given property file and splits them according to the used prefix.
124      *
125      * @param fileName the name of the file of the properties file.
126      * @return a map containing properties splitted according to the prefix.
127      */
128     @Nonnull
129     private Map<String, Properties> readProperties(@Nullable final String fileName) {
130         Map<String, Properties> result = new HashMap<>();
131         if (fileName != null) {
132             File propertiesFile = new File(fileName);
133             if (propertiesFile.exists()) {
134                 Properties allProperties = new Properties();
135                 try (FileInputStream fis = new FileInputStream(propertiesFile)) {
136                     allProperties.load(fis);
137                 } catch (IOException ioex) {
138                     this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
139                             new StringBuilder("Cannot access the file \"").append(fileName)
140                                     .append("\". Ignoring Properties.\nError: ").append(ioex.getMessage()));
141                 }
142                 if (!allProperties.isEmpty()) {
143                     for (String propertyName: allProperties.stringPropertyNames()) {
144                         int dotPos = propertyName.indexOf('.');
145                         if (dotPos >= 0) {
146                             String prefix = propertyName.substring(0, dotPos);
147                             String suffix = propertyName.substring(dotPos + 1);
148                             Properties prefixedProperties = result.get(prefix);
149                             if (prefixedProperties == null) {
150                                 prefixedProperties = new Properties();
151                                 result.put(prefix, prefixedProperties);
152                             }
153                             prefixedProperties.setProperty(suffix, allProperties.getProperty(propertyName));
154                         }
155                     }
156                 }
157             }
158         }
159         return result;
160     }
161 
162     /**
163      * Load all available generators and set them up with their properties.
164      *
165      * @param outputOption the configuration string provided to the annotation processor.
166      * @return the configured generators.
167      */
168     @Nonnull
169     private ServiceLoader<GeneratorService> setupGenerators(@Nonnull final Map<String, Properties> properties) {
170         ServiceLoader<GeneratorService> generatorServiceLoader = ServiceLoader.load(GeneratorService.class,
171                 this.getClass().getClassLoader());
172         Iterator<GeneratorService> generators = generatorServiceLoader.iterator();
173         while (generators.hasNext()) {
174             GeneratorService generator = generators.next();
175             if (properties.containsKey(generator.getPrefix())) {
176                 generator.setProperties(properties.get(generator.getPrefix()));
177             }
178         }
179         return generatorServiceLoader;
180     }
181 
182 }