Java tutorial
/* * Copyright 2014 Andrew Reitz * * Licensed 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 androiddb.internal; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.io.IOException; import java.io.Serializable; import java.io.Writer; 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.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.JavaFileObject; import tale.androiddb.DatabaseHelper; import tale.androiddb.Field; import tale.androiddb.Id; import tale.androiddb.Table; public final class ShillelaghProcessor extends AbstractProcessor { static final boolean DEBUG = false; private Map<String, AdapterObject> oneToManyCache; private ShillelaghLogger logger; private Elements elementUtils; private Types typeUtils; private Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); logger = new ShillelaghLogger(processingEnv.getMessager()); elementUtils = processingEnv.getElementUtils(); typeUtils = processingEnv.getTypeUtils(); filer = processingEnv.getFiler(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> supportTypes = Sets.newLinkedHashSet(); supportTypes.add(Table.class.getCanonicalName()); return supportTypes; } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { long startTime = System.currentTimeMillis(); Map<String, AdapterObject> tableObjectCache = Maps.newHashMap(); oneToManyCache = Maps.newHashMap(); for (TypeElement annotation : annotations) { Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation); for (Element element : elements) { String targetType = element.toString(); String classPackage = getPackageName(element); String className = getClassName((TypeElement) element, classPackage) + DatabaseHelper.$$SUFFIX; AdapterObject tableObject = new AdapterObject(element, classPackage, className, logger); logger.d("Element: " + element.toString()); logger.d("TargetType: " + targetType); logger.d("ClassPackage: " + classPackage); logger.d("ClassName: " + className); for (Element innerElement : element.getEnclosedElements()) { logger.d("Inner Elements: " + innerElement.getSimpleName().toString()); logger.d(innerElement.getKind().toString()); checkForTableId(tableObject, innerElement); checkForFields(tableObject, innerElement); } // TODO Check if multiple super types are supported // Loop through super types and parse out id/fields List<? extends TypeMirror> typeMirrors = typeUtils.directSupertypes(element.asType()); for (TypeMirror typeMirror : typeMirrors) { logger.d("SuperType: " + typeMirror.toString()); TypeElement typeElement = elementUtils.getTypeElement(typeMirror.toString()); List<? extends Element> enclosedElements = typeElement.getEnclosedElements(); for (Element enclosedElement : enclosedElements) { checkForTableId(tableObject, enclosedElement); checkForFields(tableObject, enclosedElement); } } logger.d(tableObject.toString()); if (tableObject.getIdColumnName() == null) { logger.e(String.format("%s does not have an id column. Did you forget @Id?", targetType)); } tableObjectCache.put(element.toString(), tableObject); } } // Process one to many relationships for (Map.Entry<String, AdapterObject> entry : oneToManyCache.entrySet()) { logger.d("Entry: " + entry.getKey() + " " + entry.getValue()); AdapterObject tableObject = tableObjectCache.get(entry.getKey()); tableObject.addColumn(new TableColumn(entry.getValue().getTableName().toLowerCase(), Integer.class.getName(), SqliteType.ONE_TO_MANY_CHILD)); tableObject.setIsChildTable(true); } for (AdapterObject tableObject : tableObjectCache.values()) { logger.d("Writing for " + tableObject.getTableName()); Element element = tableObject.getOriginatingElement(); try { JavaFileObject jfo = filer.createSourceFile(tableObject.getFqcn(), element); Writer writer = jfo.openWriter(); tableObject.brewJava(writer); writer.flush(); writer.close(); } catch (IOException e) { logger.e(String.format("Unable to write shillelagh classes for type %s: %s", element, e.getMessage())); } } long endTime = System.currentTimeMillis() - startTime; logger.n("Shillelagh took %d milliseconds", endTime); return true; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * Gets the package the element is in */ private String getPackageName(Element type) { return elementUtils.getPackageOf(type).getQualifiedName().toString(); } /** * Create the fully qualified class name */ private String getClassName(TypeElement type, String packageName) { int packageLen = packageName.length() + 1; return type.getQualifiedName().toString().substring(packageLen).replace('.', '$'); } /** * Check if the element has the @Id annotation if it does use that for it's id */ private void checkForTableId(AdapterObject tableObject, Element element) { // Check if user wants to use an id other than _id Id idAnnotation = element.getAnnotation(Id.class); if (idAnnotation != null) { if (element.asType().getKind() != TypeKind.LONG && !("java.lang.Long".equals(element.asType().toString()))) { logger.e("@Id must be on a long"); } // Id attribute set and continue tableObject.setIdColumnName(element.getSimpleName().toString()); } } /** * Check if the element has a @Field annotation if it does parse it and * add it to the table object */ private void checkForFields(AdapterObject tableObject, Element columnElement) { Field fieldAnnotation = columnElement.getAnnotation(Field.class); if (fieldAnnotation == null) return; /* Convert the element from a field to a type */ final Element typeElement = typeUtils.asElement(columnElement.asType()); final String type = typeElement == null ? columnElement.asType().toString() : elementUtils.getBinaryName((TypeElement) typeElement).toString(); TableColumn tableColumn = new TableColumn(columnElement, type); if (tableColumn.isBlob() && !tableColumn.isByteArray()) { if (!checkForSuperType(columnElement, Serializable.class) && !columnElement.asType().toString().equals("java.lang.Byte[]")) { logger.e(String.format( "%s in %s is not Serializable and will not be able to be converted to a byte array", columnElement.toString(), tableObject.getTableName())); } } else if (tableColumn.isOneToMany()) { // List<T> should only have one generic type. Get that type and make sure // it has @Table annotation TypeMirror typeMirror = ((DeclaredType) columnElement.asType()).getTypeArguments().get(0); if (typeUtils.asElement(typeMirror).getAnnotation(Table.class) == null) { logger.e("One to many relationship in class %s where %s is not annotated with @Table", tableObject.getTableName(), tableColumn.getColumnName()); } oneToManyCache.put(typeMirror.toString(), tableObject); TypeElement childColumnElement = elementUtils.getTypeElement(typeMirror.toString()); tableColumn.setType(getClassName(childColumnElement, getPackageName(childColumnElement))); } else if (tableColumn.getSqlType() == SqliteType.UNKNOWN) { @SuppressWarnings("ConstantConditions") Table annotation = typeElement.getAnnotation(Table.class); if (annotation == null) { logger.e(String.format( "%s in %s needs to be marked as a blob or should be " + "annotated with @Table", columnElement.toString(), tableObject.getTableName())); } tableColumn.setOneToOne(true); } tableObject.addColumn(tableColumn); } /** * Checks for a supertype returns true if element has a supertype */ private boolean checkForSuperType(Element element, Class type) { List<? extends TypeMirror> superTypes = typeUtils.directSupertypes(element.asType()); for (TypeMirror superType : superTypes) { if (superType.toString().equals(type.getName())) { return true; } } return false; } }