/*
* Copyright (c) 2004-2016 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
* which accompanies this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*
* Contributors:
* Tada AB
* Purdue University
*/
package org.postgresql.pljava.sqlgen;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLData;
import java.sql.SQLInput;
import java.sql.SQLOutput;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.BreakIterator;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.PrimitiveType;
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 static javax.lang.model.util.ElementFilter.constructorsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static javax.tools.Diagnostic.Kind;
import org.postgresql.pljava.ResultSetHandle;
import org.postgresql.pljava.ResultSetProvider;
import org.postgresql.pljava.TriggerData;
import org.postgresql.pljava.annotation.Function;
import org.postgresql.pljava.annotation.SQLAction;
import org.postgresql.pljava.annotation.SQLActions;
import org.postgresql.pljava.annotation.SQLType;
import org.postgresql.pljava.annotation.Trigger;
import org.postgresql.pljava.annotation.BaseUDT;
import org.postgresql.pljava.annotation.MappedUDT;
/**
* Annotation processor invoked by the annotations framework in javac for
* annotations of type org.postgresql.pljava.annotation.*.
*
* Simply forwards to a DDRProcessorImpl instance that is not constructed
* until the framework calls init (since there is nothing useful for the
* constructor to do until then).
*
* @author Thomas Hallgren - pre-Java6 version
* @author Chapman Flack (Purdue Mathematics) - update to Java6,
* add SQLType/SQLAction, polishing
*/
@SupportedAnnotationTypes({"org.postgresql.pljava.annotation.*"})
@SupportedOptions
({
"ddr.name.trusted", // default "java"
"ddr.name.untrusted", // default "javaU"
"ddr.implementor", // implementor when not annotated, default "PostgreSQL"
"ddr.output" // name of ddr file to write
})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class DDRProcessor extends AbstractProcessor
{
private DDRProcessorImpl impl;
@Override
public void init( ProcessingEnvironment processingEnv)
{
super.init( processingEnv);
impl = new DDRProcessorImpl( processingEnv);
}
@Override
public boolean process( Set<? extends TypeElement> tes, RoundEnvironment re)
{
if ( null == impl )
throw new IllegalStateException(
"The annotation processing framework has called process() " +
"before init()");
return impl.process( tes, re);
}
}
/**
* Where the work happens.
*/
class DDRProcessorImpl
{
// Things supplied by the calling framework in ProcessingEnvironment,
// used enough that it makes sense to break them out here with
// short names that all nested classes below will inherit.
//
final Elements elmu;
final Filer filr;
final Locale loca;
final Messager msgr;
final Map<String, String> opts;
final SourceVersion srcv;
final Types typu;
// Similarly, the TypeMapper should be easily available to code below.
//
final TypeMapper tmpr;
// Options obtained from the invocation
//
final String nameTrusted;
final String nameUntrusted;
final String output;
final String defaultImplementor;
// Certain known types that need to be recognized in the processed code
//
final DeclaredType TY_ITERATOR;
final DeclaredType TY_OBJECT;
final DeclaredType TY_RESULTSET;
final DeclaredType TY_RESULTSETPROVIDER;
final DeclaredType TY_RESULTSETHANDLE;
final DeclaredType TY_SQLDATA;
final DeclaredType TY_SQLINPUT;
final DeclaredType TY_SQLOUTPUT;
final DeclaredType TY_STRING;
final DeclaredType TY_TRIGGERDATA;
final NoType TY_VOID;
// Our own annotations
//
final TypeElement AN_FUNCTION;
final TypeElement AN_SQLACTION;
final TypeElement AN_SQLACTIONS;
final TypeElement AN_SQLTYPE;
final TypeElement AN_TRIGGER;
final TypeElement AN_BASEUDT;
final TypeElement AN_MAPPEDUDT;
DDRProcessorImpl( ProcessingEnvironment processingEnv)
{
elmu = processingEnv.getElementUtils();
filr = processingEnv.getFiler();
loca = processingEnv.getLocale();
msgr = processingEnv.getMessager();
opts = processingEnv.getOptions();
srcv = processingEnv.getSourceVersion();
typu = processingEnv.getTypeUtils();
tmpr = new TypeMapper();
String optv;
optv = opts.get( "ddr.name.trusted");
if ( null != optv )
nameTrusted = optv;
else
nameTrusted = "java";
optv = opts.get( "ddr.name.untrusted");
if ( null != optv )
nameUntrusted = optv;
else
nameUntrusted = "javaU";
optv = opts.get( "ddr.implementor");
if ( null != optv )
defaultImplementor = "-".equals( optv) ? null : optv;
else
defaultImplementor = "PostgreSQL";
optv = opts.get( "ddr.output");
if ( null != optv )
output = optv;
else
output = "pljava.ddr";
TY_ITERATOR = typu.getDeclaredType(
elmu.getTypeElement( java.util.Iterator.class.getName()));
TY_OBJECT = typu.getDeclaredType(
elmu.getTypeElement( Object.class.getName()));
TY_RESULTSET = typu.getDeclaredType(
elmu.getTypeElement( java.sql.ResultSet.class.getName()));
TY_RESULTSETPROVIDER = typu.getDeclaredType(
elmu.getTypeElement( ResultSetProvider.class.getName()));
TY_RESULTSETHANDLE = typu.getDeclaredType(
elmu.getTypeElement( ResultSetHandle.class.getName()));
TY_SQLDATA = typu.getDeclaredType(
elmu.getTypeElement( SQLData.class.getName()));
TY_SQLINPUT = typu.getDeclaredType(
elmu.getTypeElement( SQLInput.class.getName()));
TY_SQLOUTPUT = typu.getDeclaredType(
elmu.getTypeElement( SQLOutput.class.getName()));
TY_STRING = typu.getDeclaredType(
elmu.getTypeElement( String.class.getName()));
TY_TRIGGERDATA = typu.getDeclaredType(
elmu.getTypeElement( TriggerData.class.getName()));
TY_VOID = typu.getNoType( TypeKind.VOID);
AN_FUNCTION = elmu.getTypeElement( Function.class.getName());
AN_SQLACTION = elmu.getTypeElement( SQLAction.class.getName());
AN_SQLACTIONS = elmu.getTypeElement( SQLActions.class.getName());
AN_SQLTYPE = elmu.getTypeElement( SQLType.class.getName());
AN_TRIGGER = elmu.getTypeElement( Trigger.class.getName());
AN_BASEUDT = elmu.getTypeElement( BaseUDT.class.getName());
AN_MAPPEDUDT = elmu.getTypeElement( MappedUDT.class.getName());
}
void msg( Kind kind, String fmt, Object... args)
{
msgr.printMessage( kind, String.format( fmt, args));
}
void msg( Kind kind, Element e, String fmt, Object... args)
{
msgr.printMessage( kind, String.format( fmt, args), e);
}
void msg( Kind kind, Element e, AnnotationMirror a,
String fmt, Object... args)
{
msgr.printMessage( kind, String.format( fmt, args), e, a);
}
void msg( Kind kind, Element e, AnnotationMirror a, AnnotationValue v,
String fmt, Object... args)
{
msgr.printMessage( kind, String.format( fmt, args), e, a, v);
}
/**
* Key usable in a mapping from (Object, Snippet-subtype) to Snippet.
* Because there's no telling in which order a Map implementation will
* compare two keys, the class matches if either one is assignable to
* the other. That's ok as long as the Snippet-subtype is never Snippet
* itself, no Object ever has two Snippets hung on it where one extends
* the other, and getSnippet is always called for the widest of any of
* the types it may retrieve.
*/
static final class SnippetsKey
{
final Object o;
final Class<? extends Snippet> c;
SnippetsKey(Object o, Class<? extends Snippet> c)
{
assert Snippet.class != c : "Snippet key must be a subtype";
this.o = o;
this.c = c;
}
public boolean equals(Object oth)
{
if ( ! (oth instanceof SnippetsKey) )
return false;
SnippetsKey osk = (SnippetsKey)oth;
return o.equals( osk.o)
&& ( c.isAssignableFrom( osk.c) || osk.c.isAssignableFrom( c) );
}
public int hashCode()
{
return o.hashCode(); // must not depend on c (subtypes will match)
}
}
/**
* Collection of code snippets being accumulated (possibly over more than
* one round), keyed by the object for which each snippet has been
* generated.
*/
Map<SnippetsKey, Snippet> snippets = new HashMap<>();
<S extends Snippet> S getSnippet(Object o, Class<S> c)
{
return (S)snippets.get( new SnippetsKey( o, c));
}
void putSnippet( Object o, Snippet s)
{
snippets.put( new SnippetsKey( o, s.getClass()), s);
}
/**
* Queue on which snippets are entered in preparation for topological
* ordering. Has to be an instance field because populating the queue
* (which involves invoking the snippets' characterize methods) cannot
* be left to generateDescriptor, which runs in the final round. This is
* (AFAICT) another workaround for javac 7's behavior of throwing away
* symbol tables between rounds; when characterize was invoked in
* generateDescriptor, any errors reported were being shown with no source
* location info, because it had been thrown away.
*/
Queue<Vertex<Snippet>> snippetQueue = new LinkedList<>();
/**
* Map from each arbitrary provides/requires label to the snippet
* that 'provides' it. Has to be out here as an instance field for the
* same reason {@code snippetQueue} does.
*/
Map<String, Vertex<Snippet>> provider = new HashMap<>();
/**
* Set of provides/requires labels for which at least one consumer has
* been seen. An instance field for the same reason as {@code provider}.
*/
Set<String> consumer = new HashSet<>();
/**
* Find the elements in each round that carry any of the annotations of
* interest and generate code snippets accordingly. On the last round, with
* all processing complete, generate the deployment descriptor file.
*/
boolean process( Set<? extends TypeElement> tes, RoundEnvironment re)
{
boolean functionPresent = false;
boolean sqlActionPresent = false;
boolean sqlActionsPresent = false;
boolean baseUDTPresent = false;
boolean mappedUDTPresent = false;
boolean willClaim = true;
for ( TypeElement te : tes )
{
if ( AN_FUNCTION.equals( te) )
functionPresent = true;
else if ( AN_SQLACTION.equals( te) )
sqlActionPresent = true;
else if ( AN_SQLACTIONS.equals( te) )
sqlActionsPresent = true;
else if ( AN_BASEUDT.equals( te) )
baseUDTPresent = true;
else if ( AN_MAPPEDUDT.equals( te) )
mappedUDTPresent = true;
else if ( AN_SQLTYPE.equals( te) )
; // these are handled within FunctionImpl
else
{
msg( Kind.WARNING, te,
"pljava annotation processor version may be older than " +
"this annotation:\n%s", te.toString());
willClaim = false;
}
}
if ( baseUDTPresent )
for ( Element e : re.getElementsAnnotatedWith( AN_BASEUDT) )
processUDT( e, UDTKind.BASE);
if ( mappedUDTPresent )
for ( Element e : re.getElementsAnnotatedWith( AN_MAPPEDUDT) )
processUDT( e, UDTKind.MAPPED);
if ( functionPresent )
for ( Element e : re.getElementsAnnotatedWith( AN_FUNCTION) )
processFunction( e);
if ( sqlActionPresent )
for ( Element e : re.getElementsAnnotatedWith( AN_SQLACTION) )
processSQLAction( e);
if ( sqlActionsPresent )
for ( Element e : re.getElementsAnnotatedWith( AN_SQLACTIONS) )
processSQLActions( e);
tmpr.workAroundJava7Breakage(); // perhaps it will be fixed in Java 9?
if ( ! re.processingOver() )
defensiveEarlyCharacterize();
else if ( ! re.errorRaised() )
generateDescriptor();
return willClaim;
}
/**
* Iterate over collected snippets, characterize them, and enter them
* (if no error) in the data structures for topological ordering. Was
* originally the first part of {@code generateDescriptor}, but that is
* run in the final round, which is too late for javac 7 anyway, which
* throws symbol tables away between rounds. Any errors reported from
* characterize were being shown without source locations, because the
* information was gone. This may now be run more than once, so the
* {@code snippets} map is cleared before returning.
*/
void defensiveEarlyCharacterize()
{
for ( Snippet snip : snippets.values() )
{
if ( ! snip.characterize() )
continue;
Vertex<Snippet> v = new Vertex<>( snip);
snippetQueue.add( v);
for ( String s : snip.provides() )
if ( null != provider.put( s, v) )
msg( Kind.ERROR, "tag %s has more than one provider", s);
for ( String s : snip.requires() )
consumer.add( s);
}
snippets.clear();
}
/**
* Arrange the collected snippets into a workable sequence (nothing with
* requires="X" can come before whatever has provides="X"), then create
* a deployment descriptor file in proper form.
*/
void generateDescriptor()
{
boolean errorRaised = false;
for ( Vertex<Snippet> v : snippetQueue )
for ( String s : v.payload.requires() )
{
Vertex<Snippet> p = provider.get( s);
if ( null != p )
p.precede( v);
else if ( s == v.payload.implementor() ) // yes == if from impl
{
/*
* It's the implicit requires(implementor()). Bump the
* indegree anyway so the snippet won't be emitted until
* the cycle breaker code (see below) sets it free after
* any others that can be handled first.
*/
if ( ! defaultImplementor.equals( s) )
++ v.indegree;
}
else
{
msg( Kind.ERROR,
"tag \"%s\" is required but nowhere provided", s);
errorRaised = true;
}
}
if ( errorRaised )
return;
Snippet[] snips = new Snippet [ snippetQueue.size() ];
Queue<Vertex<Snippet>> q = new LinkedList<>();
for ( Iterator<Vertex<Snippet>> it = snippetQueue.iterator() ;
it.hasNext() ; )
{
Vertex<Snippet> v = it.next();
if ( 0 == v.indegree )
{
q.add( v);
it.remove();
}
}
queuerunning: for ( int i = 0 ; ; )
{
while ( ! q.isEmpty() )
{
Vertex<Snippet> v = q.remove();
snips[i++] = v.payload;
v.use( q, snippetQueue);
for ( String p : v.payload.provides() )
consumer.remove(p);
}
if ( snippetQueue.isEmpty() )
break; // all done
/*
* There are snippets remaining to output but they all have
* indegree > 0, normally a 'cycle' error. But somewhere there may
* be one with indegree exactly 1 and an implicit requirement of its
* own implementor tag, with no snippet on record to provide it.
* That's allowed (maybe the installing/removing environment will
* be "providing" that tag anyway), so set one such snippet free
* and see how much farther we get.
*/
for ( Iterator<Vertex<Snippet>> it = snippetQueue.iterator();
it.hasNext(); )
{
Vertex<Snippet> v = it.next();
if ( 1 < v.indegree || null == v.payload.implementor() )
continue;
if ( provider.containsKey( v.payload.implementor()) )
continue;
-- v.indegree;
it.remove();
q.add( v);
continue queuerunning;
}
/*
* Got here? It's a real cycle ... nothing to be done.
*/
for ( String s : consumer )
msg( Kind.ERROR, "requirement in a cycle: %s", s);
return;
}
try
{
DDRWriter.emit( snips, this);
}
catch ( IOException ioe )
{
msg( Kind.ERROR, "while writing %s: %s", output, ioe.getMessage());
}
}
/**
* Process a single element annotated with @SQLAction.
*/
void processSQLAction( Element e)
{
SQLActionImpl sa = getSnippet( e, SQLActionImpl.class);
if ( null == sa )
{
sa = new SQLActionImpl();
putSnippet( e, sa);
}
for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) )
{
if ( am.getAnnotationType().asElement().equals( AN_SQLACTION) )
populateAnnotationImpl( sa, e, am);
}
}
/**
* Process a single element annotated with @SQLActions (which simply takes
* an array of @SQLAction as a way to associate more than one SQLAction with
* a single program element)..
*/
void processSQLActions( Element e)
{
for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) )
{
if ( am.getAnnotationType().asElement().equals( AN_SQLACTIONS) )
{
SQLActionsImpl sas = new SQLActionsImpl();
populateAnnotationImpl( sas, e, am);
for ( SQLAction sa : sas.value() )
putSnippet( sa, (Snippet)sa);
}
}
}
static enum UDTKind { BASE, MAPPED }
/**
* Process a single element annotated with @BaseUDT or @MappedUDT, as
* indicated by the UDTKind k.
*/
void processUDT( Element e, UDTKind k)
{
/*
* The allowed target type for the UDT annotations is TYPE, which can
* be a class, interface (including annotation type) or enum, of which
* only CLASS is valid here. If it is anything else, just return, as
* that can only mean a source error prevented the compiler making sense
* of it, and the compiler will have its own messages about that.
*/
switch ( e.getKind() )
{
case CLASS:
break;
case ANNOTATION_TYPE:
case ENUM:
case INTERFACE:
msg( Kind.ERROR, e, "A pljava UDT must be a class");
default:
return;
}
Set<Modifier> mods = e.getModifiers();
if ( ! mods.contains( Modifier.PUBLIC) )
{
msg( Kind.ERROR, e, "A pljava UDT must be public");
}
if ( mods.contains( Modifier.ABSTRACT) )
{
msg( Kind.ERROR, e, "A pljava UDT must not be abstract");
}
if ( ! ((TypeElement)e).getNestingKind().equals(
NestingKind.TOP_LEVEL) )
{
if ( ! mods.contains( Modifier.STATIC) )
{
msg( Kind.ERROR, e,
"When nested, a pljava UDT must be static (not inner)");
}
for ( Element ee = e; null != ( ee = ee.getEnclosingElement() ); )
{
if ( ! ee.getModifiers().contains( Modifier.PUBLIC) )
msg( Kind.ERROR, ee,
"A pljava UDT must not have a non-public " +
"enclosing class");
if ( ((TypeElement)ee).getNestingKind().equals(
NestingKind.TOP_LEVEL) )
break;
}
}
switch ( k )
{
case BASE:
BaseUDTImpl bu = getSnippet( e, BaseUDTImpl.class);
if ( null == bu )
{
bu = new BaseUDTImpl( (TypeElement)e);
putSnippet( e, bu);
}
for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) )
{
if ( am.getAnnotationType().asElement().equals( AN_BASEUDT) )
populateAnnotationImpl( bu, e, am);
}
bu.registerFunctions();
break;
case MAPPED:
MappedUDTImpl mu = getSnippet( e, MappedUDTImpl.class);
if ( null == mu )
{
mu = new MappedUDTImpl( (TypeElement)e);
putSnippet( e, mu);
}
for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) )
{
if ( am.getAnnotationType().asElement().equals( AN_MAPPEDUDT) )
populateAnnotationImpl( mu, e, am);
}
break;
}
}
ExecutableElement huntFor(List<ExecutableElement> ees, String name,
boolean isStatic, TypeMirror retType, TypeMirror... paramTypes)
{
ExecutableElement quarry = null;
hunt: for ( ExecutableElement ee : ees )
{
if ( null != name && ! ee.getSimpleName().contentEquals( name) )
continue;
if ( ee.isVarArgs() )
continue;
if ( null != retType
&& ! typu.isSameType( ee.getReturnType(), retType) )
continue;
List<? extends TypeMirror> pts =
((ExecutableType)ee.asType()).getParameterTypes();
if ( pts.size() != paramTypes.length )
continue;
for ( int i = 0; i < paramTypes.length; ++i )
if ( ! typu.isSameType( pts.get( i), paramTypes[i]) )
continue hunt;
Set<Modifier> mods = ee.getModifiers();
if ( ! mods.contains( Modifier.PUBLIC) )
continue;
if ( isStatic && ! mods.contains( Modifier.STATIC) )
continue;
if ( null == quarry )
quarry = ee;
else
{
msg( Kind.ERROR, ee,
"Found more than one candidate " +
(null == name ? "constructor" : (name + " method")));
}
}
return quarry;
}
/**
* Process a single element annotated with @Function. After checking that
* it has the right modifiers to be called via pljava, analyze its type
* information and annotations and register an appropriate SQL code snippet.
*/
void processFunction( Element e)
{
/*
* METHOD is the only target type allowed for the Function annotation,
* so the only way for e to be anything else is if some source error has
* prevented the compiler making sense of it. In that case just return
* silently on the assumption that the compiler will have its own
* message about the true problem.
*/
if ( ! ElementKind.METHOD.equals( e.getKind()) )
return;
Set<Modifier> mods = e.getModifiers();
if ( ! mods.contains( Modifier.PUBLIC) )
{
msg( Kind.ERROR, e, "A pljava function must be public");
}
for ( Element ee = e; null != ( ee = ee.getEnclosingElement() ); )
{
if ( ElementKind.CLASS.equals( ee.getKind()) )
{
if ( ! ee.getModifiers().contains( Modifier.PUBLIC) )
msg( Kind.ERROR, ee,
"A pljava function must not have a non-public " +
"enclosing class");
if ( ((TypeElement)ee).getNestingKind().equals(
NestingKind.TOP_LEVEL) )
break;
}
}
FunctionImpl f = getSnippet( e, FunctionImpl.class);
if ( null == f )
{
f = new FunctionImpl( (ExecutableElement)e);
putSnippet( e, f);
}
for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) )
{
if ( am.getAnnotationType().asElement().equals( AN_FUNCTION) )
populateAnnotationImpl( f, e, am);
}
}
/**
* Populate an array of specified type from an annotation value
* representing an array.
*
* AnnotationValue's getValue() method returns Object, where the
* object is known to be an instance of one of a small set of classes.
* Populating an array when that value represents one is a common
* operation, so it is factored out here.
*/
static <T> T[] avToArray( Object o, Class<T> k)
{
boolean isEnum = k.isEnum();
@SuppressWarnings({"unchecked"})
List<? extends AnnotationValue> vs = (List<? extends AnnotationValue>)o;
@SuppressWarnings({"unchecked"})
T[] a = (T[])Array.newInstance( k, vs.size());
int i = 0;
for ( AnnotationValue av : vs )
{
Object v = getValue( av);
if ( isEnum )
v = Enum.valueOf( k.asSubclass( Enum.class),
((VariableElement)v).getSimpleName().toString());
a[i++] = k.cast( v);
}
return a;
}
/**
* Abstract superclass for synthetic implementations of annotation
* interfaces; these can be populated with element-value pairs from
* an AnnotationMirror and then used in the natural way for access to
* the values. Each subclass of this should implement the intended
* annotation interface, and should also have a
* setFoo(Object,boolean,Element) method for each foo() method in the
* interface. Rather than longwindedly using the type system to enforce
* that the needed setter methods are all there, they will be looked
* up using reflection.
*/
class AbstractAnnotationImpl implements Annotation
{
public Class<? extends Annotation> annotationType()
{
throw new UnsupportedOperationException();
}
/**
* Supply the required implementor() method for those subclasses
* that will implement {@link Snippet}.
*/
public String implementor() { return _implementor; }
String _implementor = defaultImplementor;
String _comment;
public void setImplementor( Object o, boolean explicit, Element e)
{
if ( explicit )
_implementor = "".equals( o) ? null : (String)o;
}
/**
* Use from characterize() in any subclass implementing Snippet.
*/
protected String[] augmentRequires( String req[], String imp)
{
if ( null == imp )
return req;
String[] newreq = new String [ 1 + req.length ];
System.arraycopy( req, 0, newreq, 0, req.length);
newreq[req.length] = imp;
return newreq;
}
public String comment() { return _comment; }
public void setComment( Object o, boolean explicit, Element e)
{
if ( explicit )
{
_comment = (String)o;
if ( "".equals( _comment) )
_comment = null;
}
else
_comment = ((Commentable)this).derivedComment( e);
}
public String derivedComment( Element e)
{
String dc = elmu.getDocComment( e);
if ( null == dc )
return null;
return firstSentence( dc);
}
public String firstSentence( String s)
{
BreakIterator bi = BreakIterator.getSentenceInstance( loca);
bi.setText( s);
int start = bi.first();
int end = bi.next();
if ( BreakIterator.DONE == end )
return null;
return s.substring( start, end).trim();
}
}
/**
* Populate an AbstractAnnotationImpl-derived Annotation implementation
* from the element-value pairs in an AnnotationMirror. For each element
* foo in the annotation interface, the implementation is assumed to have
* a method setFoo(Object o, boolean explicit, element e) where o is the
* element's value as obtained from AnnotationValue.getValue(), explicit
* indicates whether the element was explicitly present in the annotation
* or filled in from a default value, and e is the element carrying the
* annotation (chiefly for use as a location hint in diagnostic messages).
*
* Some of the annotation implementations below will leave certain elements
* null if they were not given explicit values, in order to have a clear
* indication that they were defaulted, even though that is not the way
* normal annotation objects behave.
*
* If a setFoo(Object o, boolean explicit, element e) method is not found
* but there is an accessible field _foo it will be set directly, but only
* if the value was explicitly present in the annotation or the field value
* is null. By this convention, an implementation can declare a field
* initially null and let its default value be filled in from what the
* annotation declares, or initially some non-null value distinct from
* possible annotation values, and be able to tell whether it was explicitly
* set. Note that a field of primitive type will never be seen as null.
*/
void populateAnnotationImpl(
AbstractAnnotationImpl inst, Element e, AnnotationMirror am)
{
Map<? extends ExecutableElement, ? extends AnnotationValue> explicit =
am.getElementValues();
Map<? extends ExecutableElement, ? extends AnnotationValue> defaulted =
elmu.getElementValuesWithDefaults( am);
// Astonishingly, even though JLS3 9.7 clearly says "annotations must
// contain an element-value pair for every element of the corresponding
// annotation type, except for those elements with default values, or a
// compile-time error occurs" - in Sun 1.6.0_39 javac never flags
// the promised error, and instead allows us to NPE on something that
// ought to be guaranteed to be there! >:[
//
// If you want something done right, you have to do it yourself....
//
Element anne = am.getAnnotationType().asElement();
List<ExecutableElement> keys = methodsIn( anne.getEnclosedElements());
for ( ExecutableElement k : keys )
if ( ! defaulted.containsKey( k) )
msg( Kind.ERROR, e, am,
"annotation missing required element \"%s\"",
k.getSimpleName());
for (
Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> me
: defaulted.entrySet()
)
{
ExecutableElement k = me.getKey();
AnnotationValue av = me.getValue();
boolean isExplicit = explicit.containsKey( k);
String name = k.getSimpleName().toString();
Class<? extends Annotation> kl = inst.getClass();
try
{
Object v = getValue( av);
kl.getMethod( // let setter for foo() be setFoo()
"set"+name.substring( 0, 1).toUpperCase() +
name.substring( 1),
Object.class, boolean.class, Element.class)
.invoke(inst, v, isExplicit, e);
}
catch (AnnotationValueException ave)
{
msg( Kind.ERROR, e, am,
"unresolved value for annotation member \"%s\"" +
" (check for missing/misspelled import, etc.)",
name);
}
catch (NoSuchMethodException nsme)
{
Object v = getValue( av);
try
{
Field f = kl.getField( "_"+name);
Class<?> fkl = f.getType();
if ( ! isExplicit && null != f.get( inst) )
continue;
if ( fkl.isArray() )
{
try {
f.set( inst, avToArray( v, fkl.getComponentType()));
}
catch (AnnotationValueException ave)
{
msg( Kind.ERROR, e, am,
"unresolved value for an element of annotation" +
" member \"%s\" (check for missing/misspelled" +
" import, etc.)",
name);
}
}
else if ( fkl.isEnum() )
f.set( inst, Enum.valueOf( fkl.asSubclass( Enum.class),
((VariableElement)v).getSimpleName().toString()));
else
f.set( inst, v);
nsme = null;
}
catch (NoSuchFieldException nsfe) { }
catch (IllegalAccessException iae) { }
if ( null != nsme )
throw new RuntimeException(
"Incomplete implementation in annotation processor",
nsme);
}
catch (IllegalAccessException iae)
{
throw new RuntimeException(
"Incorrect implementation of annotation processor", iae);
}
catch (InvocationTargetException ite)
{
String msg = ite.getCause().getMessage();
msg( Kind.ERROR, e, am, av, "%s", msg);
}
}
}
// It could be nice to have another annotation-driven tool that could just
// generate these implementations of some annotation types....
class SQLTypeImpl extends AbstractAnnotationImpl implements SQLType
{
public String value() { return _value; }
public String[] defaultValue() { return _defaultValue; }
String _value;
String[] _defaultValue;
public void setValue( Object o, boolean explicit, Element e)
{
if ( explicit )
_value = (String)o;
}
public void setDefaultValue( Object o, boolean explicit, Element e)
{
if ( explicit )
_defaultValue = avToArray( o, String.class);
}
}
class SQLActionsImpl extends AbstractAnnotationImpl implements SQLActions
{
public SQLAction[] value() { return _value; }
SQLAction[] _value;
public void setValue( Object o, boolean explicit, Element e)
{
AnnotationMirror[] ams = avToArray( o, AnnotationMirror.class);
_value = new SQLAction [ ams.length ];
int i = 0;
for ( AnnotationMirror am : ams )
{
SQLActionImpl a = new SQLActionImpl();
populateAnnotationImpl( a, e, am);
_value [ i++ ] = a;
}
}
}
class SQLActionImpl
extends AbstractAnnotationImpl
implements SQLAction, Snippet
{
public String[] install() { return _install; }
public String[] remove() { return _remove; }
public String[] provides() { return _provides; }
public String[] requires() { return _requires; }
public String[] deployStrings() { return _install; }
public String[] undeployStrings() { return _remove; }
public String[] _install;
public String[] _remove;
public String[] _provides;
public String[] _requires;
public boolean characterize()
{
_requires = augmentRequires( _requires, implementor());
return true;
}
}
class TriggerImpl
extends AbstractAnnotationImpl
implements Trigger, Snippet, Commentable
{
public String[] arguments() { return _arguments; }
public Event[] events() { return _events; }
public String name() { return _name; }
public String schema() { return _schema; }
public String table() { return _table; }
public Scope scope() { return _scope; }
public Called called() { return _called; }
public String when() { return _when; }
public String[] columns() { return _columns; }
public String[] provides() { return new String[0]; }
public String[] requires() { return new String[0]; }
/* Trigger is a Snippet but doesn't directly participate in tsort */
public String[] _arguments;
public Event[] _events;
public String _name;
public String _schema;
public String _table;
public Scope _scope;
public Called _called;
public String _when;
public String[] _columns;
FunctionImpl func;
AnnotationMirror origin;
TriggerImpl( FunctionImpl f, AnnotationMirror am)
{
func = f;
origin = am;
}
public boolean characterize()
{
if ( Scope.ROW.equals( _scope) )
{
for ( Event e : _events )
if ( Event.TRUNCATE.equals( e) )
msg( Kind.ERROR, func.func, origin,
"TRUNCATE trigger cannot be FOR EACH ROW");
}
else if ( Called.INSTEAD_OF.equals( _called) )
msg( Kind.ERROR, func.func, origin,
"INSTEAD OF trigger cannot be FOR EACH STATEMENT");
if ( ! "".equals( _when) && Called.INSTEAD_OF.equals( _called) )
msg( Kind.ERROR, func.func, origin,
"INSTEAD OF triggers do not support WHEN conditions");
if ( 0 < _columns.length )
{
if ( Called.INSTEAD_OF.equals( _called) )
msg( Kind.ERROR, func.func, origin,
"INSTEAD OF triggers do not support lists of columns");
boolean seen = false;
for ( Event e : _events )
if ( Event.UPDATE.equals( e) )
seen = true;
if ( ! seen )
msg( Kind.ERROR, func.func, origin,
"Column list is meaningless unless UPDATE is a trigger event");
}
if ( "".equals( _name) )
_name = TriggerNamer.synthesizeName( this);
return false;
}
public String[] deployStrings()
{
StringBuilder sb = new StringBuilder();
sb.append( "CREATE TRIGGER ").append( name()).append( "\n\t");
switch ( called() )
{
case BEFORE: sb.append( "BEFORE " ); break;
case AFTER: sb.append( "AFTER " ); break;
case INSTEAD_OF: sb.append( "INSTEAD OF "); break;
}
int s = _events.length;
for ( Event e : _events )
{
sb.append( e.toString());
if ( Event.UPDATE.equals( e) && 0 < _columns.length )
{
sb.append( " OF ");
int cs = _columns.length;
for ( String c : _columns )
{
sb.append( c);
if ( 0 < -- cs )
sb.append( ", ");
}
}
if ( 0 < -- s )
sb.append( " OR ");
}
sb.append( "\n\tON ");
if ( ! "".equals( schema()) )
sb.append( schema()).append( '.');
sb.append( table()).append( "\n\tFOR EACH ");
sb.append( scope().toString());
if ( ! "".equals( _when) )
sb.append( "\n\tWHEN ").append( _when);
sb.append( "\n\tEXECUTE PROCEDURE ");
func.appendNameAndParams( sb, false);
sb.setLength( sb.length() - 1); // drop closing )
s = _arguments.length;
for ( String a : _arguments )
{
sb.append( "\n\t").append( DDRWriter.eQuote( a));
if ( 0 < -- s )
sb.append( ',');
}
sb.append( ')');
String comm = comment();
if ( null == comm )
return new String[] { sb.toString() };
return new String[] {
sb.toString(),
"COMMENT ON TRIGGER " + name() + " ON " +
( "".equals( schema()) ? "" : ( schema() + '.' ) ) + table() +
"\nIS " +
DDRWriter.eQuote( comm)
};
}
public String[] undeployStrings()
{
StringBuilder sb = new StringBuilder();
sb.append( "DROP TRIGGER ").append( name()).append( "\n\tON ");
if ( ! "".equals( schema()) )
sb.append( schema()).append( '.');
sb.append( table());
return new String[] { sb.toString() };
}
}
class FunctionImpl
extends AbstractAnnotationImpl
implements Function, Snippet, Commentable
{
public String type() { return _type; }
public String name() { return _name; }
public String schema() { return _schema; }
public OnNullInput onNullInput() { return _onNullInput; }
public Security security() { return _security; }
public Effects effects() { return _effects; }
public Trust trust() { return _trust; }
public boolean leakproof() { return _leakproof; }
public int cost() { return _cost; }
public int rows() { return _rows; }
public String[] settings() { return _settings; }
public String[] provides() { return _provides; }
public String[] requires() { return _requires; }
public Trigger[] triggers() { return _triggers; }
ExecutableElement func;
public String _type;
public String _name;
public String _schema;
public OnNullInput _onNullInput;
public Security _security;
public Effects _effects;
public Trust _trust;
public Boolean _leakproof;
int _cost;
int _rows;
public String[] _settings;
public String[] _provides;
public String[] _requires;
Trigger[] _triggers;
boolean complexViaInOut = false;
boolean setof = false;
TypeMirror setofComponent = null;
boolean trigger = false;
FunctionImpl(ExecutableElement e)
{
func = e;
}
public void setCost( Object o, boolean explicit, Element e)
{
_cost = ((Integer)o).intValue();
if ( _cost < 0 && explicit )
throw new IllegalArgumentException( "cost must be nonnegative");
}
public void setRows( Object o, boolean explicit, Element e)
{
_rows = ((Integer)o).intValue();
if ( _rows < 0 && explicit )
throw new IllegalArgumentException( "rows must be nonnegative");
}
public void setTriggers( Object o, boolean explicit, Element e)
{
AnnotationMirror[] ams = avToArray( o, AnnotationMirror.class);
_triggers = new Trigger [ ams.length ];
int i = 0;
for ( AnnotationMirror am : ams )
{
TriggerImpl ti = new TriggerImpl( this, am);
populateAnnotationImpl( ti, e, am);
_triggers [ i++ ] = ti;
}
}
public boolean characterize()
{
if ( "".equals( _name) )
_name = func.getSimpleName().toString();
Set<Modifier> mods = func.getModifiers();
if ( ! mods.contains( Modifier.STATIC) )
{
msg( Kind.ERROR, func, "A pljava function must be static");
}
TypeMirror ret = func.getReturnType();
if ( ret.getKind().equals( TypeKind.ERROR) )
{
msg( Kind.ERROR, func,
"Unable to resolve return type of function");
return false;
}
ExecutableType et = (ExecutableType)func.asType();
List<? extends TypeMirror> ptms = et.getParameterTypes();
int arity = ptms.size();
if ( ! "".equals( type())
&& ret.getKind().equals( TypeKind.BOOLEAN) )
{
complexViaInOut = true;
TypeMirror tm = ptms.get( arity - 1);
if ( tm.getKind().equals( TypeKind.ERROR)
// unresolved things seem assignable to anything
|| ! typu.isSameType( tm, TY_RESULTSET) )
{
msg( Kind.ERROR, func.getParameters().get( arity - 1),
"Last parameter of complex-type-returning function " +
"must be ResultSet");
return false;
}
}
else if ( typu.isAssignable( typu.erasure( ret), TY_ITERATOR) )
{
setof = true;
List<TypeMirror> pending = new LinkedList<>();
pending.add( ret);
while ( ! pending.isEmpty() )
{
TypeMirror tm = pending.remove( 0);
if ( typu.isSameType( typu.erasure( tm), TY_ITERATOR) )
{
DeclaredType dt = (DeclaredType)tm;
List<? extends TypeMirror> typeArgs =
dt.getTypeArguments();
if ( 1 != typeArgs.size() )
{
msg( Kind.ERROR, func,
"Need one type argument for Iterator " +
"return type");
return false;
}
setofComponent = typeArgs.get( 0);
break;
}
else
{
pending.addAll( typu.directSupertypes( tm));
}
}
if ( null == setofComponent )
{
msg( Kind.ERROR, func,
"Failed to find setof component type");
return false;
}
}
else if ( typu.isAssignable( ret, TY_RESULTSETPROVIDER)
|| typu.isAssignable( ret, TY_RESULTSETHANDLE) )
{
setof = true;
}
else if ( ret.getKind().equals( TypeKind.VOID) && 1 == arity )
{
TypeMirror tm = ptms.get( 0);
if ( ! tm.getKind().equals( TypeKind.ERROR)
// unresolved things seem assignable to anything
&& typu.isSameType( tm, TY_TRIGGERDATA) )
{
trigger = true;
}
}
if ( ! setof && -1 != rows() )
msg( Kind.ERROR, func,
"ROWS specified on a function not returning SETOF");
if ( ! trigger && 0 != _triggers.length )
msg( Kind.ERROR, func,
"a function with triggers needs void return and " +
"one TriggerData parameter");
/*
* Report any unmappable types now that could appear in
* deployStrings (return type or parameter types) ... so that the
* error messages won't be missing the source location, as they can
* with javac 7 throwing away symbol tables between rounds.
* Because the logic in deployStrings determining what to call
* getSQLType on is a bit fiddly, the simplest way to make all those
* calls here is just ... call deployStrings.
*/
deployStrings();
_requires = augmentRequires( _requires, implementor());
for ( Trigger t : triggers() )
((TriggerImpl)t).characterize();
return true;
}
/**
* Append SQL syntax for the function's name (schema-qualified if
* appropriate) and parameters, either with any defaults indicated
* (for use in CREATE FUNCTION) or without (for use in DROP FUNCTION).
*
* @param dflts Whether to include the defaults, if any.
*/
void appendNameAndParams( StringBuilder sb, boolean dflts)
{
if ( ! "".equals( schema()) )
sb.append( schema()).append( '.');
sb.append( name()).append( '(');
appendParams( sb, dflts);
// TriggerImpl relies on ) being the very last character
sb.append( ')');
}
void appendParams( StringBuilder sb, boolean dflts)
{
if ( ! trigger )
{
ExecutableType et = (ExecutableType)func.asType();
List<? extends TypeMirror> tms = et.getParameterTypes();
Iterator<? extends VariableElement> ves =
func.getParameters().iterator();
if ( complexViaInOut )
tms = tms.subList( 0, tms.size() - 1);
int s = tms.size();
for ( TypeMirror tm : tms )
{
VariableElement ve = ves.next();
sb.append( "\n\t").append( ve.getSimpleName().toString());
sb.append( ' ');
sb.append( tmpr.getSQLType( tm, ve, true, dflts));
if ( 0 < -- s )
sb.append( ',');
}
}
}
void appendAS( StringBuilder sb)
{
Element e = func.getEnclosingElement();
if ( ! e.getKind().equals( ElementKind.CLASS) )
msg( Kind.ERROR, func,
"Somehow this method got enclosed by something other " +
"than a class");
sb.append( e.toString()).append( '.');
sb.append( trigger ? func.getSimpleName() : func.toString());
}
public String[] deployStrings()
{
ArrayList<String> al = new ArrayList<>();
StringBuilder sb = new StringBuilder();
sb.append( "CREATE OR REPLACE FUNCTION ");
appendNameAndParams( sb, true);
sb.append( "\n\tRETURNS ");
if ( trigger )
sb.append( "trigger");
else
{
if ( setof )
sb.append( "SETOF ");
if ( ! "".equals( type()) )
sb.append( type());
else if ( null != setofComponent )
sb.append( tmpr.getSQLType( setofComponent, func));
else if ( setof )
sb.append( "RECORD");
else
sb.append( tmpr.getSQLType( func.getReturnType(), func));
}
sb.append( "\n\tLANGUAGE ");
if ( Trust.SANDBOXED.equals( trust()) )
sb.append( nameTrusted);
else
sb.append( nameUntrusted);
sb.append( ' ').append( effects());
if ( leakproof() )
sb.append( " LEAKPROOF");
sb.append( '\n');
if ( OnNullInput.RETURNS_NULL.equals( onNullInput()) )
sb.append( "\tRETURNS NULL ON NULL INPUT\n");
if ( Security.DEFINER.equals( security()) )
sb.append( "\tSECURITY DEFINER\n");
if ( -1 != cost() )
sb.append( "\tCOST ").append( cost()).append( '\n');
if ( -1 != rows() )
sb.append( "\tROWS ").append( rows()).append( '\n');
for ( String s : settings() )
sb.append( "\tSET ").append( s).append( '\n');
sb.append( "\tAS '");
appendAS( sb);
sb.append( '\'');
al.add( sb.toString());
String comm = comment();
if ( null != comm )
{
sb.setLength( 0);
sb.append( "COMMENT ON FUNCTION ");
appendNameAndParams( sb, false);
sb.append( "\nIS ");
sb.append( DDRWriter.eQuote( comm));
al.add( sb.toString());
}
for ( Trigger t : triggers() )
for ( String s : ((TriggerImpl)t).deployStrings() )
al.add( s);
return al.toArray( new String [ al.size() ]);
}
public String[] undeployStrings()
{
String[] rslt = new String [ 1 + triggers().length ];
int i = rslt.length - 1;
for ( Trigger t : triggers() )
for ( String s : ((TriggerImpl)t).undeployStrings() )
rslt [ --i ] = s;
StringBuilder sb = new StringBuilder();
sb.append( "DROP FUNCTION ");
appendNameAndParams( sb, false);
rslt [ rslt.length - 1 ] = sb.toString();
return rslt;
}
}
static enum BaseUDTFunctionID
{
INPUT( "in", "cstring, oid, integer", null),
OUTPUT( "out", null, "cstring"),
RECEIVE( "recv", "internal, oid, integer", null),
SEND( "send", null, "bytea");
BaseUDTFunctionID( String suffix, String param, String ret)
{
this.suffix = suffix;
this.param = param;
this.ret = ret;
}
private String suffix;
private String param;
private String ret;
String getSuffix() { return suffix; }
String getParam( BaseUDTImpl u)
{
if ( null != param )
return param;
return u.qname;
}
String getRet( BaseUDTImpl u)
{
if ( null != ret )
return ret;
return u.qname;
}
}
class BaseUDTFunctionImpl extends FunctionImpl
{
BaseUDTFunctionImpl(
BaseUDTImpl ui, TypeElement te, BaseUDTFunctionID id)
{
super( null);
this.ui = ui;
this.te = te;
this.id = id;
_type = id.getRet( ui);
_name = ui.name() + '_' + id.getSuffix();
_schema = ui.schema();
_cost = -1;
_rows = -1;
_onNullInput = OnNullInput.CALLED;
_security = Security.INVOKER;
_effects = Effects.VOLATILE;
_trust = Trust.SANDBOXED;
_leakproof = false;
_settings = new String[0];
_triggers = new Trigger[0];
_provides = _settings;
_requires = _settings;
}
BaseUDTImpl ui;
TypeElement te;
BaseUDTFunctionID id;
@Override
void appendParams( StringBuilder sb, boolean dflts)
{
sb.append( id.getParam( ui));
}
@Override
void appendAS( StringBuilder sb)
{
sb.append( "UDT[").append( te.toString()).append( "] ");
sb.append( id.name());
}
StringBuilder appendTypeOp( StringBuilder sb)
{
sb.append( id.name()).append( " = ");
if ( ! "".equals( schema()) )
sb.append( schema()).append( '.');
return sb.append( name());
}
@Override
public boolean characterize()
{
return false;
}
public void setType( Object o, boolean explicit, Element e)
{
if ( explicit )
msg( Kind.ERROR, e,
"The type of a UDT function may not be changed");
}
public void setRows( Object o, boolean explicit, Element e)
{
if ( explicit )
msg( Kind.ERROR, e,
"The rows attribute of a UDT function may not be set");
}
public void setProvides( Object o, boolean explicit, Element e)
{
if ( explicit )
msg( Kind.ERROR, e,
"A UDT function does not have its own provides/requires");
}
public void setRequires( Object o, boolean explicit, Element e)
{
if ( explicit )
msg( Kind.ERROR, e,
"A UDT function does not have its own provides/requires");
}
public void setTriggers( Object o, boolean explicit, Element e)
{
if ( explicit )
msg( Kind.ERROR, e,
"A UDT function may not have associated triggers");
}
public void setImplementor( Object o, boolean explicit, Element e)
{
if ( explicit )
msg( Kind.ERROR, e,
"A UDT function does not have its own implementor");
}
public String implementor()
{
return ui.implementor();
}
public String derivedComment( Element e)
{
String comm = super.derivedComment( e);
if ( null != comm )
return comm;
return id.name() + " method for type " + ui.qname;
}
}
abstract class AbstractUDTImpl
extends AbstractAnnotationImpl
implements Snippet, Commentable
{
public String name() { return _name; }
public String schema() { return _schema; }
public String[] provides() { return _provides; }
public String[] requires() { return _requires; }
public String[] _provides;
public String[] _requires;
public String _name;
public String _schema;
TypeElement tclass;
String qname;
AbstractUDTImpl(TypeElement e)
{
tclass = e;
if ( ! typu.isAssignable( e.asType(), TY_SQLDATA) )
{
msg( Kind.ERROR, e, "A pljava UDT must implement %s",
TY_SQLDATA);
}
ExecutableElement niladicCtor = huntFor(
constructorsIn( tclass.getEnclosedElements()), null, false,
null);
if ( null == niladicCtor )
{
msg( Kind.ERROR, tclass,
"A pljava UDT must have a public no-arg constructor");
}
}
protected void setQname()
{
if ( "".equals( _name) )
_name = tclass.getSimpleName().toString();
if ( "".equals( _schema) )
qname = _name;
else
qname = _schema + "." + _name;
}
protected void addComment( ArrayList<String> al)
{
String comm = comment();
if ( null == comm )
return;
al.add( "COMMENT ON TYPE " + qname + "\nIS " +
DDRWriter.eQuote( comm));
}
}
class MappedUDTImpl
extends AbstractUDTImpl
implements MappedUDT
{
public String[] structure() { return _structure; }
String[] _structure;
public void setStructure( Object o, boolean explicit, Element e)
{
if ( explicit )
_structure = avToArray( o, String.class);
}
MappedUDTImpl(TypeElement e)
{
super( e);
}
public boolean characterize()
{
setQname();
_requires = augmentRequires( _requires, implementor());
return true;
}
public String[] deployStrings()
{
ArrayList<String> al = new ArrayList<>();
if ( null != structure() )
{
StringBuilder sb = new StringBuilder();
sb.append( "CREATE TYPE ").append( qname).append( " AS (");
int i = structure().length;
for ( String s : structure() )
sb.append( "\n\t").append( s).append(
( 0 < -- i ) ? ',' : '\n');
sb.append( ')');
al.add( sb.toString());
}
al.add( "SELECT sqlj.add_type_mapping(" +
DDRWriter.eQuote( qname) + ", " +
DDRWriter.eQuote( tclass.toString()) + ')');
addComment( al);
return al.toArray( new String [ al.size() ]);
}
public String[] undeployStrings()
{
ArrayList<String> al = new ArrayList<>();
al.add( "SELECT sqlj.drop_type_mapping(" +
DDRWriter.eQuote( qname) + ')');
if ( null != structure() )
al.add( "DROP TYPE " + qname);
return al.toArray( new String [ al.size() ]);
}
}
class BaseUDTImpl
extends AbstractUDTImpl
implements BaseUDT
{
public String typeModifierInput() { return _typeModifierInput; }
public String typeModifierOutput() { return _typeModifierOutput; }
public String analyze() { return _analyze; }
public int internalLength() { return _internalLength; }
public boolean passedByValue() { return _passedByValue; }
public Alignment alignment() { return _alignment; }
public Storage storage() { return _storage; }
public String like() { return _like; }
public char category() { return _category; }
public boolean preferred() { return _preferred; }
public String defaultValue() { return _defaultValue; }
public String element() { return _element; }
public char delimiter() { return _delimiter; }
public boolean collatable() { return _collatable; }
BaseUDTFunctionImpl in, out, recv, send;
public String _typeModifierInput;
public String _typeModifierOutput;
public String _analyze;
int _internalLength;
public Boolean _passedByValue;
Alignment _alignment;
Storage _storage;
public String _like;
char _category;
public Boolean _preferred;
String _defaultValue;
public String _element;
char _delimiter;
public Boolean _collatable;
boolean lengthExplicit;
boolean alignmentExplicit;
boolean storageExplicit;
boolean categoryExplicit;
boolean delimiterExplicit;
public void setInternalLength( Object o, boolean explicit, Element e)
{
_internalLength = (Integer)o;
lengthExplicit = explicit;
}
public void setAlignment( Object o, boolean explicit, Element e)
{
_alignment = Alignment.valueOf(
((VariableElement)o).getSimpleName().toString());
alignmentExplicit = explicit;
}
public void setStorage( Object o, boolean explicit, Element e)
{
_storage = Storage.valueOf(
((VariableElement)o).getSimpleName().toString());
categoryExplicit = explicit;
}
public void setDefaultValue( Object o, boolean explicit, Element e)
{
if ( explicit )
_defaultValue = (String)o; // "" could be a real default value
}
public void setCategory( Object o, boolean explicit, Element e)
{
_category = (Character)o;
categoryExplicit = explicit;
}
public void setDelimiter( Object o, boolean explicit, Element e)
{
_delimiter = (Character)o;
delimiterExplicit = explicit;
}
BaseUDTImpl(TypeElement e)
{
super( e);
}
void registerFunctions()
{
setQname();
ExecutableElement instanceReadSQL = huntFor(
methodsIn( tclass.getEnclosedElements()), "readSQL", false,
TY_VOID, TY_SQLINPUT, TY_STRING);
ExecutableElement instanceWriteSQL = huntFor(
methodsIn( tclass.getEnclosedElements()), "writeSQL", false,
TY_VOID, TY_SQLOUTPUT);
ExecutableElement instanceToString = huntFor(
methodsIn( tclass.getEnclosedElements()), "toString", false,
TY_STRING);
ExecutableElement staticParse = huntFor(
methodsIn( tclass.getEnclosedElements()), "parse", true,
tclass.asType(), TY_STRING, TY_STRING);
if ( null == staticParse )
{
msg( Kind.ERROR, tclass,
"A pljava UDT must have a public static " +
"parse(String,String) method that returns the UDT");
}
else
{
in = new BaseUDTFunctionImpl(
this, tclass, BaseUDTFunctionID.INPUT);
putSnippet( staticParse, in);
}
out = new BaseUDTFunctionImpl(
this, tclass, BaseUDTFunctionID.OUTPUT);
putSnippet( null != instanceToString ? instanceToString : out, out);
recv = new BaseUDTFunctionImpl(
this, tclass, BaseUDTFunctionID.RECEIVE);
putSnippet( null != instanceReadSQL ? instanceReadSQL : recv, recv);
send = new BaseUDTFunctionImpl(
this, tclass, BaseUDTFunctionID.SEND);
putSnippet( null != instanceWriteSQL ? instanceWriteSQL : send,
send);
}
public boolean characterize()
{
if ( "".equals( typeModifierInput())
&& ! "".equals( typeModifierOutput()) )
msg( Kind.ERROR, tclass,
"UDT typeModifierOutput useless without typeModifierInput");
if ( 1 > internalLength() && -1 != internalLength() )
msg( Kind.ERROR, tclass,
"UDT internalLength must be positive, or -1 for varying");
if ( passedByValue() &&
( 8 < internalLength() || -1 == internalLength() ) )
msg( Kind.ERROR, tclass,
"Only a UDT of fixed length <= 8 can be passed by value");
if ( -1 == internalLength() &&
-1 == alignment().compareTo( Alignment.INT4) )
msg( Kind.ERROR, tclass,
"A variable-length UDT must have alignment at least INT4");
if ( -1 != internalLength() && Storage.PLAIN != storage() )
msg( Kind.ERROR, tclass,
"Storage for a fixed-length UDT must be PLAIN");
// see PostgreSQL backend/commands/typecmds.c "must be simple ASCII"
if ( 32 > category() || category() > 126 )
msg( Kind.ERROR, tclass,
"UDT category must be a printable ASCII character");
_requires = augmentRequires( _requires, implementor());
return true;
}
public String[] deployStrings()
{
ArrayList<String> al = new ArrayList<>();
al.add( "CREATE TYPE " + qname);
al.addAll( Arrays.asList( in.deployStrings()));
al.addAll( Arrays.asList( out.deployStrings()));
al.addAll( Arrays.asList( recv.deployStrings()));
al.addAll( Arrays.asList( send.deployStrings()));
StringBuilder sb = new StringBuilder();
sb.append( "CREATE TYPE ").append( qname).append( " (\n\t");
in.appendTypeOp( sb).append( ",\n\t");
out.appendTypeOp( sb).append( ",\n\t");
recv.appendTypeOp( sb).append( ",\n\t");
send.appendTypeOp( sb);
if ( ! "".equals( typeModifierInput()) )
sb.append( ",\n\tTYPMOD_IN = ").append( typeModifierInput());
if ( ! "".equals( typeModifierOutput()) )
sb.append( ",\n\tTYPMOD_OUT = ").append( typeModifierOutput());
if ( ! "".equals( analyze()) )
sb.append( ",\n\tANALYZE = ").append( typeModifierOutput());
if ( lengthExplicit || "".equals( like()) )
sb.append( ",\n\tINTERNALLENGTH = ").append(
-1 == internalLength() ? "VARIABLE"
: String.valueOf( internalLength()));
if ( passedByValue() )
sb.append( ",\n\tPASSEDBYVALUE");
if ( alignmentExplicit || "".equals( like()) )
sb.append( ",\n\tALIGNMENT = ").append( alignment().name());
if ( storageExplicit || "".equals( like()) )
sb.append( ",\n\tSTORAGE = ").append( storage().name());
if ( ! "".equals( like()) )
sb.append( ",\n\tLIKE = ").append( like());
if ( categoryExplicit )
sb.append( ",\n\tCATEGORY = '").append(
DDRWriter.eQuote( String.valueOf( category())));
if ( preferred() )
sb.append( ",\n\tPREFERRED = true");
if ( null != defaultValue() )
sb.append( ",\n\tDEFAULT = ").append(
DDRWriter.eQuote( defaultValue()));
if ( ! "".equals( element()) )
sb.append( ",\n\tELEMENT = ").append( element());
if ( delimiterExplicit )
sb.append( ",\n\tDELIMITER = '").append(
DDRWriter.eQuote( String.valueOf( delimiter())));
if ( collatable() )
sb.append( ",\n\tCOLLATABLE = true");
al.add( sb.append( "\n)").toString());
addComment( al);
return al.toArray( new String [ al.size() ]);
}
public String[] undeployStrings()
{
return new String[]
{
"DROP TYPE " + qname + " CASCADE"
};
}
}
/**
* Provides the default mappings from Java types to SQL types.
*/
class TypeMapper
{
ArrayList<Map.Entry<Class<?>, String>> protoMappings;
ArrayList<Map.Entry<TypeMirror, String>> finalMappings;
TypeMapper()
{
protoMappings = new ArrayList<>();
// Primitives
//
this.addMap(boolean.class, "boolean");
this.addMap(Boolean.class, "boolean");
this.addMap(byte.class, "smallint");
this.addMap(Byte.class, "smallint");
this.addMap(char.class, "smallint");
this.addMap(Character.class, "smallint");
this.addMap(double.class, "double precision");
this.addMap(Double.class, "double precision");
this.addMap(float.class, "real");
this.addMap(Float.class, "real");
this.addMap(int.class, "integer");
this.addMap(Integer.class, "integer");
this.addMap(long.class, "bigint");
this.addMap(Long.class, "bigint");
this.addMap(short.class, "smallint");
this.addMap(Short.class, "smallint");
// Known common mappings
//
this.addMap(Number.class, "numeric");
this.addMap(String.class, "varchar");
this.addMap(java.util.Date.class, "timestamp");
this.addMap(Timestamp.class, "timestamp");
this.addMap(Time.class, "time");
this.addMap(java.sql.Date.class, "date");
this.addMap(BigInteger.class, "numeric");
this.addMap(BigDecimal.class, "numeric");
this.addMap(ResultSet.class, "record");
this.addMap(Object.class, "\"any\"");
this.addMap(byte[].class, "bytea");
}
/*
* What worked in Java 6 was to keep a list of Class<?> -> sqltype
* mappings, and get TypeMirrors from the Classes at the time of trying
* to identify types (in the final, after-all-sources-processed round).
* Starting in Java 7, you get different TypeMirror instances in
* different rounds for the same types, so you can't match something
* seen in round 1 to something looked up in the final round. (However,
* you can match things seen in round 1 to things looked up prior to
* the first round, when init() is called and constructs the processor.)
*
* So, this method needs to be called at the end of round 1 (or at the
* end of every round, it just won't do anything but once), and at that
* point it will compute the list order and freeze a list of TypeMirrors
* to avoid looking up the Class<?>es later and getting different
* mirrors.
*
* This should work as long as all the sources containg pljava
* annotations will be found in round 1. That would only not be the case
* if some other annotation processor is in use that could generate new
* sources with pljava annotations in them, requiring additional rounds.
* In the present state of things, that simply won't work. Java bug
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8038455 might
* cover this, and promises a fix in Java 9, but who knows?
*/
private void workAroundJava7Breakage()
{
if ( null != finalMappings )
return; // after the first round, it's too late!
// Need to check more specific types before those they are
// assignable to by widening reference conversions, so a
// topological sort is in order.
//
List<Vertex<Map.Entry<Class<?>, String>>> vs = new ArrayList<>(
protoMappings.size());
for ( Map.Entry<Class<?>, String> me : protoMappings )
vs.add( new Vertex<>( me));
for ( int i = vs.size(); i --> 1; )
{
Vertex<Map.Entry<Class<?>, String>> vi = vs.get( i);
Class<?> ci = vi.payload.getKey();
for ( int j = i; j --> 0; )
{
Vertex<Map.Entry<Class<?>, String>> vj = vs.get( j);
Class<?> cj = vj.payload.getKey();
boolean oij = ci.isAssignableFrom( cj);
boolean oji = cj.isAssignableFrom( ci);
if ( oji == oij )
continue; // no precedence constraint between these two
if ( oij )
vj.precede( vi);
else
vi.precede( vj);
}
}
Queue<Vertex<Map.Entry<Class<?>, String>>> q = new LinkedList<>();
for ( Vertex<Map.Entry<Class<?>, String>> v : vs )
if ( 0 == v.indegree )
q.add( v);
finalMappings = new ArrayList<>( protoMappings.size());
protoMappings.clear();
while ( ! q.isEmpty() )
{
Vertex<Map.Entry<Class<?>, String>> v = q.remove();
v.use( q);
Class<?> k = v.payload.getKey();
TypeMirror ktm;
if ( k.isPrimitive() )
{
TypeKind tk =
TypeKind.valueOf( k.getName().toUpperCase());
ktm = typu.getPrimitiveType( tk);
}
else
{
TypeElement te =
elmu.getTypeElement( k.getName());
if ( null == te ) // can't find it -> not used in code?
{
msg( Kind.WARNING,
"Found no TypeElement for %s", k.getName());
continue; // hope it wasn't one we'll need!
}
ktm = te.asType();
}
finalMappings.add(
new AbstractMap.SimpleImmutableEntry<TypeMirror, String>(
ktm, v.payload.getValue()));
}
}
/**
* Add a custom mapping from a Java class to an SQL type.
*
* @param k Class representing the Java type
* @param v String representing the SQL type to be used
*/
void addMap(Class<?> k, String v)
{
if ( null != finalMappings )
{
msg( Kind.ERROR,
"addMap(%s, %s)\n" +
"called after workAroundJava7Breakage", k.getName(), v);
return;
}
protoMappings.add(
new AbstractMap.SimpleImmutableEntry<Class<?>, String>( k, v));
}
/**
* Return the SQL type for the Java type represented by a TypeMirror,
* from an explicit annotation if present, otherwise by applying the
* default mappings. No default-value information is included in the
* string returned. It is assumed that a function return is being typed
* rather than a function parameter.
*
* @param tm Represents the type whose corresponding SQL type is wanted.
* @param e Annotated element (chiefly for use as a location hint in
* diagnostic messages).
*/
String getSQLType(TypeMirror tm, Element e)
{
return getSQLType( tm, e, false, false);
}
/**
* Return the SQL type for the Java type represented by a TypeMirror,
* from an explicit annotation if present, otherwise by applying the
* default mappings.
*
* @param tm Represents the type whose corresponding SQL type is wanted.
* @param e Annotated element (chiefly for use as a location hint in
* diagnostic messages).
* @param contravariant Indicates that the element whose type is wanted
* is a function parameter and should be given the widest type that can
* be assigned to it. If false, find the narrowest type that a function
* return can be assigned to.
* @param withDefault Indicates whether any specified default value
* information should also be included in the "type" string returned.
*/
String getSQLType(TypeMirror tm, Element e,
boolean contravariant, boolean withDefault)
{
boolean array = false;
String rslt = null;
String[] defaults = null;
for ( AnnotationMirror am : elmu.getAllAnnotationMirrors( e) )
{
if ( am.getAnnotationType().asElement().equals( AN_SQLTYPE) )
{
SQLTypeImpl sti = new SQLTypeImpl();
populateAnnotationImpl( sti, e, am);
rslt = sti.value();
defaults = sti.defaultValue();
}
}
if ( tm.getKind().equals( TypeKind.ARRAY) )
{
ArrayType at = ((ArrayType)tm);
if ( ! at.getComponentType().getKind().equals( TypeKind.BYTE) )
{
array = true;
tm = at.getComponentType();
// only for bytea[] should this ever still be an array
}
}
if ( null != rslt )
return typeWithDefault( rslt, array, defaults, withDefault);
if ( tm.getKind().equals( TypeKind.VOID) )
return "void"; // can't be a parameter type so no defaults apply
if ( tm.getKind().equals( TypeKind.ERROR) )
{
msg ( Kind.ERROR, e,
"Cannot determine mapping to SQL type for unresolved type");
rslt = tm.toString();
}
else
{
ArrayList<Map.Entry<TypeMirror, String>> ms = finalMappings;
if ( contravariant )
{
ms = (ArrayList<Map.Entry<TypeMirror, String>>)ms.clone();
Collections.reverse( ms);
}
for ( Map.Entry<TypeMirror, String> me : ms )
{
TypeMirror ktm = me.getKey();
if ( ktm instanceof PrimitiveType )
{
if ( typu.isSameType( tm, ktm) )
{
rslt = me.getValue();
break;
}
}
else
{
boolean accept;
if ( contravariant )
accept = typu.isAssignable( ktm, tm);
else
accept = typu.isAssignable( tm, ktm);
if ( accept )
{
// don't compute a type of Object/"any" for
// a function return (just admit defeat instead)
if ( contravariant
|| ! typu.isSameType( ktm, TY_OBJECT) )
rslt = me.getValue();
break;
}
}
}
}
if ( null == rslt )
{
msg( Kind.ERROR, e,
"No known mapping to an SQL type");
rslt = tm.toString();
}
if ( array )
rslt += "[]";
return typeWithDefault( rslt, array, defaults, withDefault);
}
/**
* Given the matching SQL type already determined, return it with or
* without default-value information appended, as the caller desires.
* To ensure that the generated descriptor will be in proper form, the
* default values are emitted as properly-escaped string literals and
* then cast to the appropriate type. This approach will not work for
* defaults given as arbitrary SQL expressions, but covers the typical
* cases of simple literals and even anything that can be computed as
* a Java String constant expression (e.g. ""+Math.PI).
*
* @param rslt The bare SQL type string already determined
* @param array Whether the Java type was determined to be an array
* @param defaults Array (null if not present) of default value strings
* @param withDefault Whether to append the default information to the
* type.
*/
String typeWithDefault(
String rslt, boolean array, String[] defaults, boolean withDefault)
{
if ( null == defaults || ! withDefault )
return rslt;
int n = defaults.length;
if ( n != 1 )
array = true;
else if ( ! array )
array = arrayish.matcher( rslt).matches();
StringBuilder sb = new StringBuilder( rslt);
sb.append( " DEFAULT CAST(");
if ( array )
sb.append( "ARRAY[");
if ( n != 1 )
sb.append( "\n\t");
for ( String s : defaults )
{
sb.append( DDRWriter.eQuote( s));
if ( 0 < -- n )
sb.append( ",\n\t");
}
if ( array )
sb.append( ']');
sb.append( " AS ").append( rslt).append( ')');
return sb.toString();
}
}
// expression intended to match SQL types that are arrays
static final Pattern arrayish =
Pattern.compile( "(?si:(?:\\[\\s*+\\d*+\\s*+\\]|ARRAY)\\s*+)$");
/**
* Work around bizarre javac behavior that silently supplies an Error
* class in place of an attribute value for glaringly obvious source errors,
* instead of reporting them.
* @param av AnnotationValue to extract the value from
* @return The result of getValue unless {@code av} is an error placeholder
*/
static Object getValue( AnnotationValue av)
{
if ( "com.sun.tools.javac.code.Attribute.Error".equals(
av.getClass().getCanonicalName()) )
throw new AnnotationValueException();
return av.getValue();
}
}
/**
* Exception thrown when an expected annotation value is a compiler-internal
* Error class instead, which happens in some javac versions when the annotation
* value wasn't resolved because of a source error the compiler really should
* have reported.
*/
class AnnotationValueException extends RuntimeException { }
/**
* A code snippet. May contain zero, one, or more complete SQL commands for
* each of deploying and undeploying. The commands contained in one Snippet
* will always be emitted in a fixed order. A collection of Snippets will be
* output in an order constrained by their provides and requires methods.
*/
interface Snippet
{
/**
* An {@code <implementor name>} that will be used to wrap each command
* from this Snippet as an {@code <implementor block>}. If null, the
* commands will be emitted as plain {@code <SQL statement>}s.
*/
public String implementor();
/**
* Return an array of SQL commands (one complete command to a string) to
* be executed in order during deployment.
*/
public String[] deployStrings();
/**
* Return an array of SQL commands (one complete command to a string) to
* be executed in order during undeployment.
*/
public String[] undeployStrings();
/**
* Return an array of arbitrary labels considered "provided" by this
* Snippet. In generating the final order of the deployment descriptor file,
* this Snippet will come before any whose requires method returns any of
* the same labels.
*/
public String[] provides();
/**
* Return an array of arbitrary labels considered "required" by this
* Snippet. In generating the final order of the deployment descriptor file,
* this Snippet will come after those whose provides method returns any of
* the same labels.
*/
public String[] requires();
/**
* Method to be called on the final round, after all annotations'
* element/value pairs have been filled in, to compute any additional
* information derived from those values before deployStrings() or
* undeployStrings() can be called.
* @return true if this Snippet is standalone and should be scheduled and
* emitted based on provides/requires; false if something else will emit it.
*/
public boolean characterize();
}
interface Commentable
{
public String comment();
public void setComment( Object o, boolean explicit, Element e);
public String derivedComment( Element e);
}
/**
* Vertex in a DAG, as used to put things in workable topological order
*/
class Vertex<P>
{
P payload;
int indegree;
List<Vertex<P>> adj;
Vertex( P payload)
{
this.payload = payload;
indegree = 0;
adj = new ArrayList<>();
}
void precede( Vertex<P> v)
{
++ v.indegree;
adj.add( v);
}
void use( Collection<Vertex<P>> q)
{
for ( Vertex<P> v : adj )
if ( 0 == -- v.indegree )
q.add( v);
}
void use( Collection<Vertex<P>> q, Collection<Vertex<P>> vs)
{
for ( Vertex<P> v : adj )
if ( 0 == -- v.indegree )
{
vs.remove( v);
q.add( v);
}
}
}