blob: b74130c04e4da1c0ec8f0303f9764a662f8913dd [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.qpid.server.plugin;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.NoSuchFileException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.apache.qpid.server.License;
public class PluggableProcessor extends AbstractProcessor
{
private Map<String, Set<String>> factoryImplementations = new HashMap<>();
@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.latest();
}
@Override
public Set<String> getSupportedAnnotationTypes()
{
return Collections.singleton(PluggableService.class.getName());
}
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv)
{
if(roundEnv.processingOver())
{
generateServiceFiles(processingEnv.getFiler());
return true;
}
try
{
for (Element e : roundEnv.getElementsAnnotatedWith(PluggableService.class))
{
if (e.getKind() == ElementKind.CLASS)
{
TypeElement classElement = (TypeElement) e;
Set<String> pluggableTypes = getPluggableTypes(classElement);
for(String pluggableType : pluggableTypes)
{
Set<String> existingFactories = factoryImplementations.get(pluggableType);
if(existingFactories == null)
{
existingFactories = new HashSet<>();
factoryImplementations.put(pluggableType, existingFactories);
}
existingFactories.add(classElement.getQualifiedName().toString());
}
}
}
}
catch (Exception e)
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Error: " + e.getLocalizedMessage());
}
return true;
}
private Set<String> getPluggableTypes(final TypeElement classElement)
{
final Set<String> types = new HashSet<>();
List<? extends TypeMirror> interfaces = classElement.getInterfaces();
for(TypeMirror typeMirror : interfaces)
{
TypeElement interfaceElt = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
if(interfaceElt.getQualifiedName().toString().equals("org.apache.qpid.server.plugin.Pluggable"))
{
types.add(classElement.getQualifiedName().toString());
}
else
{
types.addAll(getPluggableTypes(interfaceElt));
}
}
TypeMirror superClass = classElement.getSuperclass();
if(!(superClass instanceof NoType))
{
types.addAll(getPluggableTypes((TypeElement) processingEnv.getTypeUtils().asElement(superClass)));
}
return types;
}
private void generateServiceFiles(Filer filer)
{
for(String serviceName : factoryImplementations.keySet())
{
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.NOTE, "Generating service file for " + serviceName);
String relativeName = "META-INF/services/" + serviceName;
loadExistingServicesFile(filer, serviceName);
try
{
FileObject serviceFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", relativeName);
try(PrintWriter pw = new PrintWriter(new OutputStreamWriter(serviceFile.openOutputStream(), "UTF-8")))
{
for (String headerLine : License.LICENSE)
{
pw.println("#" + headerLine);
}
pw.println("#");
pw.println("# Note: Parts of this file are auto-generated from annotations.");
pw.println("#");
for (String implementation : factoryImplementations.get(serviceName))
{
pw.println(implementation);
}
}
}
catch (IOException e)
{
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR,
"Failed to write services file: "
+ relativeName
+ " - "
+ e.getLocalizedMessage()
);
}
}
}
private String loadExistingServicesFile(final Filer filer, String serviceName)
{
String relativeName = "META-INF/services/" + serviceName;
try
{
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", relativeName);
try(BufferedReader r = new BufferedReader(new InputStreamReader(existingFile.openInputStream(), "UTF-8")))
{
String line;
while ((line = r.readLine()) != null)
{
if (!line.matches(" *#"))
{
factoryImplementations.get(serviceName).add(line);
}
}
}
}
catch (NoSuchFileException | FileNotFoundException e)
{
// no existing file (ignore)
}
catch (IOException e)
{
String errorMessage;
try(StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw))
{
e.printStackTrace(pw);
errorMessage = sw.toString();
}
catch (IOException ioe)
{
errorMessage = e.getLocalizedMessage();
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Error loading existing services file: " + relativeName
+ " - " + errorMessage);
}
return relativeName;
}
}